LOJ#2336「JOI 2017 Final」繩

Address

LOJ#2336

Solution

題目的限制有些複雜,先做一些簡化。

首先染色一定不會摺疊的過程中進行,因爲厚度可以疊加的限制恰好保證了在摺疊過程中染色不會比一開始染色更優,並且也沒有必要把同一根線染色多次。

由此可知,染色之後的繩子至多隻有兩種顏色。

一個關鍵的結論:

染色之後繩長可以縮短至 22 的充分必要條件是:除了首尾兩段,其餘的同色連續段的長度都爲偶數。

證明:

  1. 充分性:

    即需要對於每種滿足該條件的繩子構造出一種方案,使得繩長能夠縮短至 22

    • 首先將最後一段的長度縮短至 11

    • 若倒數第二段不是第一段,取倒數第二段的中線,將繩子沿着這條中線翻折,翻折之後倒數第二段變爲最後一段,重複上述過程;

    • 若倒數第二段是第一段,將第一段的長度縮短至 11,總的繩長縮短至 22,摺疊結束。

  2. 必要性:

    即需要證明不滿足該條件的繩子都無法構造出方案,使得繩長能夠縮短至 22

    • 取出既不是首段也不是尾段的某一個長度爲奇數的段,考慮每一次摺疊的影響;
    • 因爲與該段相鄰的是兩個異色的段,每次摺疊所取的中線一定不能在該段內;
    • 對於所取中線在該段外的摺疊,摺疊過後與該段相鄰的仍是兩個異色的段;
    • 只要與該段相鄰的是兩個異色的段,繩長就永遠無法被縮短至 22

因爲長度爲偶數的連續段可以劃分爲若干個長度爲 22 的小段,我們只需要枚舉第一個長度爲 22 的小段是在位置 11 還是在位置 22,保證每一個長度爲 22 的小段內線的顏色都相同,最後在兩種情況中取最優解。

考慮枚舉其中一種顏色 ii,計算包含顏色 ii 的答案。

討論一個長度爲 22 的小段中的幾種情況:

  1. 如果小段內兩根線同色且都爲 ii,顯然不需要再更改;
  2. 如果小段內其中一根線顏色爲 ii,將另一根線也改爲 ii 顯然不會更劣;
  3. 如果小段內兩根線的顏色都不爲 ii,則需要根據所取的另一種顏色來決定費用。

cnticnt_i 表示初始時顏色 ii 的出現次數,fi,jf_{i,j} 表示兩根線的顏色恰好爲 i,ji,j 的小段數,則我們所求即爲 minji{ncnticntj+fi,j}\min \limits_{j \neq i}\{n - cnt_i - cnt_j + f_{i,j}\}

考慮枚舉每個顏色爲 ii 的線的出現位置,若與其同處一個小段的線的顏色爲 j(ji)j(j \neq i),則令 cntjcnt_j 減一,需要動態維護 maxji{cntj}\max\limits_{j \neq i}\{cnt_j\},可以用一個桶維護,支持 O(1)\mathcal O(1) 修改和查詢。

總的時間複雜度 O(n)\mathcal O(n)

Code

#include <bits/stdc++.h>

const int S = 1 << 20;
char frd[S], *ihead = frd + S;
const char *itail = ihead;

inline char nxtChar()
{
	if (ihead == itail)
		fread(frd, 1, S, stdin), ihead = frd;
	return *ihead++;
}

template <class T>
inline void read(T &res)
{
	char ch; 
	while (ch = nxtChar(), !isdigit(ch));
	res = ch ^ 48;
	while (ch = nxtChar(), isdigit(ch))
		res = res * 10 + ch - 48;
} 

char fwt[S], *ohead = fwt;
const char *otail = ohead + S;

inline void outChar(char ch)
{
	if (ohead == otail)
		fwrite(fwt, 1, S, stdout), ohead = fwt;
	*ohead++ = ch;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	outChar(x % 10 + 48);
}

using std::vector;
const int N = 1e6 + 5;
const int Maxn = 0x3f3f3f3f;

vector<int> v[N];
int n, m, mx;
int cnt[N], apr[N], ans[N], col[N], mth[N];

template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
template <class T>
inline void CkMax(T &x, T y) {x < y ? x = y : 0;}

inline void del(int x)
{
	--apr[cnt[x]];
	!apr[cnt[x]] && cnt[x] == mx ? --mx : 0;
	++apr[--cnt[x]];
}

inline void add(int x)
{
	--apr[cnt[x]];
	cnt[x] == mx ? ++mx : 0;
	++apr[++cnt[x]];
}

inline void solve()
{
	for (int i = 1; i <= m; ++i)
	{
		int tmp = cnt[i];
		for (int j = 1; j <= tmp; ++j)
			del(i);
		for (int j = 0, jm = v[i].size(); j < jm; ++j)
		{
			int x = v[i][j];
			if (mth[x] && col[mth[x]] != i)
				del(col[mth[x]]);
		}
		CkMin(ans[i], n - tmp - mx);
		for (int j = 0, jm = v[i].size(); j < jm; ++j)
		{
			int x = v[i][j];
			if (mth[x] && col[mth[x]] != i)
				add(col[mth[x]]);
		}
		for (int j = 1; j <= tmp; ++j)
			add(i);
	}
}

int main()
{
	read(n); read(m);
	if (m == 1)
		return puts("0"), 0;

	for (int i = 1; i <= m; ++i)
		ans[i] = Maxn;
	for (int i = 1; i <= n; ++i)
	{
		read(col[i]);
		v[col[i]].push_back(i);
		++cnt[col[i]];
	}	
	for (int i = 1; i <= m; ++i)
		CkMax(mx, cnt[i]), ++apr[cnt[i]];
	for (int i = 1; i <= n; i += 2)
		if (i < n)
			mth[i] = i + 1, mth[i + 1] = i;
		else
			mth[i] = 0;
	solve();

	mth[1] = 0;
	for (int i = 2; i <= n; i += 2)
		if (i < n)
			mth[i] = i + 1, mth[i + 1] = i;
		else
			mth[i] = 0;
	solve();

	for (int i = 1; i <= m; ++i)
		put(ans[i]), outChar('\n');
	fwrite(fwt, 1, ohead - fwt, stdout);
	return 0;	
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章