LOJ#2336「JOI 2017 Final」绳

Address

LOJ#2336

Solution

题目的限制有些复杂,先做一些简化。

首先染色一定不会折叠的过程中进行,因为厚度可以叠加的限制恰好保证了在折叠过程中染色不会比一开始染色更优,并且也没有必要把同一根线染色多次。

由此可知,染色之后的绳子至多只有两种颜色。

一个关键的结论:

染色之后绳长可以缩短至 2 2 2 的充分必要条件是:除了首尾两段,其余的同色连续段的长度都为偶数。

证明:

  1. 充分性:

    即需要对于每种满足该条件的绳子构造出一种方案,使得绳长能够缩短至 2 2 2

    • 首先将最后一段的长度缩短至 1 1 1

    • 若倒数第二段不是第一段,取倒数第二段的中线,将绳子沿着这条中线翻折,翻折之后倒数第二段变为最后一段,重复上述过程;

    • 若倒数第二段是第一段,将第一段的长度缩短至 1 1 1,总的绳长缩短至 2 2 2,折叠结束。

  2. 必要性:

    即需要证明不满足该条件的绳子都无法构造出方案,使得绳长能够缩短至 2 2 2

    • 取出既不是首段也不是尾段的某一个长度为奇数的段,考虑每一次折叠的影响;
    • 因为与该段相邻的是两个异色的段,每次折叠所取的中线一定不能在该段内;
    • 对于所取中线在该段外的折叠,折叠过后与该段相邻的仍是两个异色的段;
    • 只要与该段相邻的是两个异色的段,绳长就永远无法被缩短至 2 2 2

因为长度为偶数的连续段可以划分为若干个长度为 2 2 2 的小段,我们只需要枚举第一个长度为 2 2 2 的小段是在位置 1 1 1 还是在位置 2 2 2,保证每一个长度为 2 2 2 的小段内线的颜色都相同,最后在两种情况中取最优解。

考虑枚举其中一种颜色 i i i,计算包含颜色 i i i 的答案。

讨论一个长度为 2 2 2 的小段中的几种情况:

  1. 如果小段内两根线同色且都为 i i i,显然不需要再更改;
  2. 如果小段内其中一根线颜色为 i i i,将另一根线也改为 i i i 显然不会更劣;
  3. 如果小段内两根线的颜色都不为 i i i,则需要根据所取的另一种颜色来决定费用。

c n t i cnt_i cnti 表示初始时颜色 i i i 的出现次数, f i , j f_{i,j} fi,j 表示两根线的颜色恰好为 i , j i,j i,j 的小段数,则我们所求即为 min ⁡ j ≠ i { n − c n t i − c n t j + f i , j } \min \limits_{j \neq i}\{n - cnt_i - cnt_j + f_{i,j}\} j=imin{ncnticntj+fi,j}

考虑枚举每个颜色为 i i i 的线的出现位置,若与其同处一个小段的线的颜色为 j ( j ≠ i ) j(j \neq i) j(j=i),则令 c n t j cnt_j cntj 减一,需要动态维护 max ⁡ j ≠ i { c n t j } \max\limits_{j \neq i}\{cnt_j\} j=imax{cntj},可以用一个桶维护,支持 O ( 1 ) \mathcal O(1) O(1) 修改和查询。

总的时间复杂度 O ( n ) \mathcal O(n) 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;	
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值