这题居然是CF2400

题意简析

  • 有一个数组 \(a_i\) 需要确定一个顺序,把每个\(a_i\)都遍历一遍。
  • 所得顺序记为 \(p_i\) 。需要保证对任意的 \(i\) , 都有 \(a_{p_i}\) \(\not=\) \(a_{p_{i+1}}\)
  • \(p\) 的花费为有多少个 \(i\) 满足 \(|p_{i+1}-p_i|>1\) ,求花费的最小值。没有则输出 \(-1\)
  • 有多组数据

分析

本蒟蒻居然第一个想到的是模拟+贪心

题意很简单,要求不相邻的个数要最小,马上想到贪心,最好一直走相邻的不一样的数,直到相邻数相同,再跳过去选一个不同的。那我们应该怎么走怎么选呢?

首先,对于无解的情况:
显然,当一个标签出现的次数大于 \(\left\lceil\dfrac{n}{2}\right\rceil\) 时,肯定是无解的。换句话说,如果 \(a_i\) 出现次数大于 \(\left\lceil\dfrac{n}{2}\right\rceil\) ,则输出 \(-1\)

接下来考虑有解的情况。我们可以将整个 \(a\) 数组分成几个小段进行讨论。
假设 \(p\) 是满足条件的排列,我们可以在 \(p\) 中不相邻的相邻索引之间添加一个分隔符。如果 \(k\)\(p\) 中“跳跃”的数量,那么我们已经将数组 \(a\) 分成 \(k+1\) 个连续的段。我们可以按正向或反向顺序扫描每个段。然后对片段进行重新排序和反转,以使没有两个相邻的标签相等。

显然,对于 \(a_i=a_{i+1}\) 我们需要在 \(i\)\(i+1\) 之间进行切割。现在,我们只需要考虑这些段的端点,并且我们需要一个条件,以便何时可以避免连接相等的端点。对于标签 \(x\) ,将 \(f_x\) 定义为以 \(x\) 显示为段的端点的次数。请注意,如果一个段仅包含一个标记,我们将
\(x\)计数两次,作为左右端点。

当且仅当对于所有标签 \(x\) ,它都保持 \(f_x\) 时,才能对段进行有效的重新排序。如果存在解,那么 \(f_x≤k+2\)

简单证一下:
考虑将段重新排序为 \((y_1\)\(y_2)\)\((y_3\)\(y_4)\),…,\((y_{2k+1}\)\(y_{2k+2})\) 。其中, \(y_{2i-1}\)\(y_{2i}\) 是按照解决方案顺序出现在第 \(i\) 个分段的端点。由于 \(y_{2i}\)\(y_{2i+1}\) 连接在一起,它们不能都是 \(x\) 。它可以是 \(y_1=x\) ,也可以是 \(y_{2k+2}=x\) ,但由于这些连接,最多一半的剩余端点(其中有 \(2k\) )可以是 \(x\) 。所以 \(f_x≤k+2\)

我们已证明如果 \(f_x≤k+2\) 对于所有标签 \(x\) ,我们可以构造一个解决方案。我们继续在 \(k\) 上进行归纳。如果 \(k=0\) ,则只有一个段,我们就完成了。如果 \(k≥1\) .让 \(x\) 是最大 \(f_x\) 的任何标签。选择一个以 \(x\) 为端点的段和另一个以 \(y\) 为端点的段( \(y\not=x\) )作为端点(注意,这样的一对总是存在的)。将选定的 \(x\)\(y\) 连接在一起,将它们合并为一个段。这样段的数量减少了 \(1\) 个,作为端点的 \(x\)\(y\) 的频率减少了 \(1\) 个。进行此连接后,该条件显然适用于 \(x\)\(y\) 。对于其他标签 \(z\)\(z\not=x\) 、 $ z\not=y$ ),在操作之前保持 \(f_z\le(2k+2)-f_x \le(2k+2)-f_z\)\(f_z≤k+1\) 。操作后, \(k\) 减小, \(f_z\) 不变,因此 \(f_z≤k+2\) 成立。通过归纳,存在解决方案。

为了找到切割次数最少的解决方案,我们首先必须切割相同标签的所有相邻索引 之间的数组。如果这组切割已经满足上述条件,我们就完成了。

否则,有一个标记 \(x\) ,使得 \(f_x > k + 2\),对于所有其他标记 \(y\not=x\) 、 $ f_y≤k+2$ 。如果我们在两个连续标签之间添加一个切割,使得其中一个是 \(x\) ,则 \(f_x\) 增加一个, \(k\) 增加一个,因此它是无用的。如果我们在两个不是 \(x\) 的连续标签之间切割,它不会改变 \(f_x\) 并且它会将 \(k\) 增加一个。也就是说,每一次这样的削减都使条件更接**等。因此,如果存在一个解并且 \(f_x>k+2\) ,我们正好需要 \(f_x - (k + 2 )\) 次额外削减。如果 \(f_x<k+2\) 则不用再削减了。

总结

如果某些标签 \(x\) 具有超过 \(\left\lceil\dfrac{n}{2}\right\rceil\) 发生时,不存在解决方案。否则,令 \(k\) 为相邻相等标签的数量,并在这些位置添加必要的切割。设 \(x\) 是在结果段中作为端点出现频率最高的标签。答案是 \(k + \max ({0,f_x-(k+2)})\)

分析完后代码就很容易了。

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5;
inline int max(int x, int y){return x > y ? x : y;}
inline int read(){
	int x = 0, f = 1; char c = getchar();
	while (! isdigit(c)) {
		if (c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + c - '0';
		c = getchar();
	}
	return x * f;
}
int T, n, k, maxx;
int a[N], f[N], t[N];
//f记录每个标签的出现次数,t记录每个标签作为端点的次数
int main(){
	T = read();
	while (T --) {
		maxx = 0;
		memset(f, 0, sizeof(f));memset(t, 0,sizeof(t));
		k = 0;
		bool ch = false;
		n = read();
		for (int i = 1; i <= n; i ++) {
		    a[i] = read();
			f[a[i]] ++;
			if(f[a[i]] > (n + 1) / 2) ch = true;
		} 
		if (ch) {//无解
			printf("-1\n");
			continue;
		}
		t[a[1]] ++; t[a[n]] ++;//首尾也要计数
		for (int i = 1; i < n; i ++) {
			if(a[i] == a[i + 1]) {
				k ++;//多分段一次
				t[a[i]] ++;
				t[a[i + 1]] ++;
			}
		} 
		for (int i = 0; i < n; i ++)
			maxx = max(maxx, t[a[i]]);//记录最大值
		printf("%d\n", k + max(0, maxx - (k + 2)));
	}
	return 0;
}