题意:N(N<=50000)个数1, 2, …, N围成一个圈,每次可以指定m个数
,把b1
放到b2
的位置,把b2
放到b3
的位置,……把bm
放到b1
的位置,代价为m。要求每个数和指定的两个数相邻,求最小代价或报告方案不存在。
为什么要看题解呢?看到这题在CodeVS上分在大师级别,又听说要用到群论,题意还没搞明白就失去了信心TAT 如果全是自己想出来的就好了。
考虑简化问题,对于0, 1, …, N-1的一个排列,将它变换为另一个排列,问最小代价。
问题一定有解。如图所示:
可以看出最小代价等于不在正确位置上的数字个数。
首先,不在正确位置上的数字个数是代价的一个下界,因为这些数必须换位置,要换位置就至少贡献1代价。这个下界是可以达到的。考虑第1个不在正确位置的数,它应该在哪里呢?1号位置。1号位置的数应该摆在哪里呢?8号位置。……5号位置的数应该摆在哪里呢?0号位置。构成一个环。现在对它们进行操作。复原。对剩下不在正确位置的数进行同样的处理。于是,如果这个算法正确,那么我们就证明了下界的可达性。
提问x的正确位置在哪里,只有一个确定的答案,于是每个点的出度为1。如果一个点不在正确位置,那么有且只有一个点应该替换掉它,于是每个点的入读为1。从而,我们证明了最小代价等于不在正确位置上的数字的个数。
给定每个数和指定两个数相邻,可以把环构造出来,如果存在。无法构造出一个环,表明无解。
将初始环0, 1, …, N变换为目标环等价于把目标环变换为初始环。可以把环断成链,和0, 1, ..., N-1
、1, 2, ..., N-1, 0
……还有N, N-1, ..., 0
、0, N, ..., 2, 1
……做比较取最少的不在正确位置上的数字个数即可。
这个算法的时间复杂度是 O(n2) 。能不能更优呢?
我有两个思路,一是在0, 1, ..., N-1
和1, 2, ..., N-1, 0
的结果之间建立联系,二是换一个角度计算。发现第二种是可行的。
做比较的时候我们实际上在干什么?对于x = 0, 1, …, N-1,统计有多少个 ai=(i−x)modN ,有多少个 ai=(x−i)modN ,取最大,用N去减。欣喜地发现,对于某个i,分别有唯一的x使之成立,于是,把贡献加到x上即可。
我觉得,难点不在于从30分到100分,而在于从0分到30分。面对一个全新的问题,怎样产生有益的想法呢?
#include
#include
using namespace std;
const int MAX_N = 50000;
int ch[MAX_N][2], d[MAX_N], l[MAX_N], r[MAX_N], t[2][MAX_N];
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d %d", &ch[i][0], &ch[i][1]);
--ch[i][0];
--ch[i][1];
++d[ch[i][0]];
++d[ch[i][1]];
}
for (int i = 0; i < n; ++i)
if (d[i] != 2) {
puts("-1");
return 0;
}
l[0] = ch[0][0];
r[0] = ch[0][1];
int x = 0, cnt = 0;
do {
int y = r[x];
l[y] = x;
r[y] = ch[y][0] == x ? ch[y][1] : ch[y][0];
x = y;
++cnt;
} while (x != 0);
if (cnt != n) {
puts("-1");
return 0;
}
for (int i = 0, x = 0; i < n; ++i, x = r[x])
++t[0][(i-x+n)%n];
for (int i = 0, x = 0; i < n; ++i, x = l[x])
++t[1][(i-x+n)%n];
int ans = 0;
for (int i = 0; i < n; ++i)
ans = max(ans, max(t[0][i], t[1][i]));
printf("%d\n", n-ans);
return 0;
}