COGS上的标签是图论+群论。。(-__-)我可以说是乱搞吗。
我们首先要构建出末态的环,如果构建不出来(比如A想和B相邻而B不想和A相邻)则无解。构建出来后我们把它变成一个序列。把初态也变成一个序列,就成了这样:
A1, A2, A3, A4……
B1, B2, B3, B4……
为什么初态用一个数组表示而不直接1,2,3,4……呢?因为把环拆开可以有不同的断点,也可以有两个方向去读取这个环。对于上面状态的两个序列,从初态变为末态要花费的代价为Σ(Ai != Bi),这里应该就是涉及群论的地方了,应该不难想明白这个代价吧。
那么我们的任务就明确了,初态末态都可以拆成2*n中序列,代价就是两个序列有多少对不同的对应元素,不过不需要2*n个与2*n个比较,只需要2*n个与1个比较即可。但时间上仍然无法接受。每次调整都是把i位置变到i+1,把n变到1(和题目中描述的指令一样),变完后再翻转序列再来一次最终得到这2*n个序列,再O(n)比较会T。
不过思考每次调整的本质,可以说是把每个数与它对应的位置的距离改变,比如说:
1 2 3 4 5
2 1 4 5 3
dis[i]表示i在末态中是初态中位置右边的第几个,dis[i]:
1 4 2 4 4
那么我们调整一次末态(初态也可以),变成:
3 2 1 4 5
那么dis数组就变成了:
2 0 3 0 0
dis[i] == 0就表示位置相同,这时只有两个位置不同,那么代价就是2,就是这一问的答案。
所以就比较显然了,我们再搞一个数组d[i]表示距离为i的数的个数,找出最大值num,那么就是说在当前初态与当前末态中,距离为num的数的个数最多,那么我们把每个数都右移num个位置,或者调整num次就可以得到上下元素不同的对数最小的情况。
当然这只是n个序列的,还需要把最开始求出的末态数组反转一下,同样的过程,就是另外n个序列的。num取max,最终n-num就是答案。
更像乱搞么,原谅蒟蒻对群论的理解不多。
#include <cstdio>
#include <algorithm>
#include <cstring>
#define M 50005
using namespace std;
int n, num, a[M], d[M], q[M][2];
bool vis[M];
void get(int &x){
char c = getchar(); x = 0;
while(c < '0' || c > '9') c = getchar();
while(c <= '9' && c >= '0') x = x*10+c-48, c = getchar();
}
bool sec(int i, int j){
a[i] = j;
vis[j] = 1;
if(i == n){
return (q[j][1]==a[i-1]&&q[j][0]==a[1])
||(q[j][0]==a[i-1]&&q[j][1]==a[1]);
}
if(!vis[q[j][1]]){
return sec(i+1, q[j][1]);
}
if(!vis[q[j][0]]) {
return sec(i+1, q[j][0]);
}
return 0;
}
int main()
{
get(n);
for(int i = 1; i <= n; i++){
get(q[i][1]); get(q[i][0]);
}
if(!sec(1, 1)){
printf("-1"); return 0;
}
for(int i = 1; i <= n; i++){
int dis = (a[i]-i+n)%n;
d[dis]++;
num = max(num, d[dis]);
}
for(int i = 1; i <= n-i+1; i++){
swap(a[i], a[n-i+1]);
}
memset(d, 0, sizeof d);
for(int i = 1; i <= n; i++){
int dis = (a[i]-i+n)%n;
d[dis]++;
num = max(num, d[dis]);
}
printf("%d", n-num);
return 0;
}