pku1721 CARDS(置换群的幂运算) ???

http://acm.pku.edu.cn/JudgeOnline/problem?id=1721

一下分析转至潘震皓的《置换群快速幂运算 研究与探讨》

 

洗牌机 (CEOI 1998) 此题对于我老菜来说是属于难题,但是通过这篇经典的文章,学到了很多~~赞下潘震皓。。。。

[问题描述]

剀剀和凡凡有N张牌(依次标号为12,……,N)和一台洗牌机。假设N是奇数。洗牌机的功能是进行如下的操作:对所有位置I1IN),如果位置I上的牌是J,而且位置J上的牌是K,那么通过洗牌机后位置I上的牌将是K

剀剀首先写下一个1~N的排列ai,在位置ai处放上数值ai+1的牌,得到的顺序x1, x2, ..., xN作为初始顺序。他把这种顺序排列的牌放入洗牌机洗牌S次,得到牌的顺序为p1, p2, ..., pN。现在,剀剀把牌的最后顺序和洗牌次数告诉凡凡,要凡凡猜出牌的最初顺序x1, x2, ..., xN

[输入]

第一行为整数NS1N10001S1000。第二行为牌的最终顺序p1, p2, ..., pN

[输出]

为一行,即牌的最初顺序x1, x2, ..., xN

[算法分析]

很显然,这题的一副扑克牌就是一个置换,而每一次洗牌就是这个置换的平方运算。由于牌的数量是奇数,并且一开始是一个大循环,所以做平方运算时候不会分裂。所以,在任意时间,牌的顺序所表示的置换一定是一个大循环。

那么根据文章开头提到的定理:设T^k=e,(T为一循环,e为单位置换),那么k的最小正整数解为T的长度。

可以知道,这个循环的n次方是单位循环,换句话说,如果k mod n=1,那么这个循环的k次方,就是它本身。我们知道,每一次洗牌是一次简单的平方运算,洗x次就是原循环的2x次方。

因为n是奇数,2x mod n=1一定有一个<n的整数解,假设这个解是a;那也就是说,一幅牌,洗a次,就会回到原来的顺序。使用最终顺序不停地洗,直到回到原始顺序,求出循环节长度a以后,再单纯地向前模拟a-s次,就可以得到原始顺序了。

上面的算法是出题方给出的标准算法。显然,时间复杂度为O(n2+logs)

换一个方向:给定了结果和s以后,可以简单地将这个目标置换用3.1节的方法开方s次得到结果。时间复杂度为O(n*s)

或者可以更简单地,算出2s,将目标置换直接开2s次方。这里有一个技巧,因为在开方时只需要在循环中前进2s次,所以我们只关心(2s) mod n,也就免去了大数字的运算。所以,计算2s需要O(logs),而开方需要O(n)。整个时间复杂度为O(n+logs)

#include<iostream> using namespace std; const int MAX=1001; int a[MAX],b[MAX]; int n,s; int main() { int i,j,k; while(scanf("%d%d",&n,&s) != EOF) { for(i=1; i<=n; i++) scanf("%d",&a[i]); //=======================目标循环 b[1]=1; //求出置换后的结果存放在数组b[]中; //printf("%d ",b[1]); i = j = 1; while(a[j] != 1) { j = a[j]; i++; b[i] = j; // printf("%d ",b[i]); } //printf("/n"); //========================求位移的步数 k = 1; for(i=1; i<=s; i++) { k *= 2; if(k >= n) k -= n; } //printf("%d/n",k); ///========================求开方运算 a[1] = b[1]; //开放运算后的结果存放在数组a[]中; j = 1; for(i=2; i<=n; i++) { j += k; if(j > n) j -= n; a[j] = b[i]; } //for(i=1; i<=n; i++) // printf("%d ",a[i]); //printf("/n"); /////======================== for(i=1; i<=n-1; i++) b[a[i]] = a[i+1]; b[a[n]] = a[1]; for(i=1; i<=n; i++) printf("%d/n",b[i]); } return 0; } 

 

你可能感兴趣的:(算法,n2)