您的位置:首页 > 其它

NOIP 2005 篝火晚会 COGS 112(只是用到置换的一个小概念而已)

2015-09-25 14:32 375 查看
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: