以下内容来自转载:
涉及到组合数学的问题,首先是群的概念:
设G是一个集合,*是G上的二元运算,如果(G,*)满足下面的条件:
封闭性:对于任何a,b∈G,有a*b∈G;
结合律:对任何a,b,c∈G有(a*b)*c=a*(b*c);
单位元:存在e∈G,使得对所有的a∈G,都有a*e=e*a=a;
逆元:对于每个元素a∈G,存在x∈G,使得a*x=x*a=e,这个时候记x为a-1,称为a的逆元,那么则称(G,*)为一个群。
例:G={0,1,2,3,4....n-1}那么它在mod n加法下是一个群。
群元素的个数有限,称为有限群,且其中元素的个数称为阶,记为|G|,群元素的个数无限,称为无限群。
若对于群元素中的任意两个元素a,b都有ab=ba那么称G为交换群,简称Abel群。
=============================================================================================
置换:设X为一个有限集,π是X到X的一个--变换,那么称π是X上的一个置换。
例:设X={1,2,3,4....n},设π是X的一个变换,满足π:1->a1,2->a2,......n->an,其中a1,a2...an是X的一个排列,则称π是X上的一个置换。
可将π记为 1 2 ...... n
a1 a2 ......a n
同一置换用这样的表示法有n!种,但其对应的关系不变。
假设循环π只这样一个置换,满足π:a1->a2,a2->a3,.............ak->a1,但是对于其他元素保持不变,即:a->a,
可将π记为 a1 a2 ...... ak
a2 a3 ...... a1
称为k阶循环,K为循环长度。
每个置换都可以写成若干个互不相交的循环的乘积,且表示是唯一的.
如 1 2 3 4 5 6
2 4 5 1 3 6 ,则可以表示为(124)(35)(6),置换的循环节数是上面的循环个数,上面的例题的循环节数为3.
=============================================================================================
定义:设G是有限集X上的置换群,点a,b∈X称为"等价"的,当且仅当,存在π∈G使得π(a)=b,记为a~b,这种等价条件下,X的元素形成的等价类称为G的轨道,它是集X的一个子集,G的任意两个不同的轨道之交是空集,所以置换群G的轨道全体是集合X的一个划分,构成若干个等价类,等价类的个数记为L。
Zk (K不动置换类):设G是1…n的置换群。若K是1…n中某个元素,G中使K保持不变的置换的全体,记以Zk,叫做G中使K保持不动的置换类,简称K不动置换类。
Ek(等价类):设G是1…n的置换群。若K是1…n中某个元素,K在G作用下的轨迹,记作Ek。即K在G的作用下所能变化成的所有元素的集合。.
这个时候有:|Ek|*|Zk|=|G|成立(k=1,2,.....n)。
C(π):对于一个置换π∈G,及a∈X,若π(a)=a,则称a为π的不动点。π的不动点的全体记为C(π)。例如π=(123)(3)(45)(6)(7),X={1,2,3,4,5,6,7};那么C(π)={3,6,7}共3个元素。
Burnside引理:L=1/|G|*(Z1+Z2+Z3+Z4+......Zk)=1/|G|*(C(π1)+C(π2)+C(π3)+.....+C(πn))(其中k∈X,π∈G)。
Polya定理:设G={π1,π2,π3........πn}是X={a1,a2,a3.......an}上一个置换群,用m中颜色对X中的元素进行涂色,那么不同的涂色方案数为:1/|G|*(mC(π1)+mC(π2)+mC(π3)+...+mC(πk)). 其中C(πk)为置换πk的循环节的个数。
polya定理求循环节个数代码模板:
- const int MAX=1001;
- #define CLR(arr,val) memset(arr,val,sizeof(arr))
- int n,perm[MAX],visit[MAX];
- int gcd(int n,int m)
- { return m==0?n:gcd(m,n%m);
- }
- void Polya()
- { int pos,sum=0;
- CLR(visit,0);
- for(int i=0;i<n;i++)
- if(!visit[i])
- { sum++;
- pos=i;
- for(int j=0;!visit[perm[pos]];j++)
- { pos=perm[pos];
- visit[pos]=1;
- }
- }
- return sum;
- }
一般可以证明:当只有旋转的时候(顺时针或逆时针),对于一个有n个字符的环,可顺时针或逆时针旋转几个位置,由于至少有n个置换,但是假设我顺时针旋转k个位置,他就等同于逆时针转动n-k个位置,假设一个置换为:G={π0,π1,π2,π3,π4,...,πn-1},这个时候可以证明逆时针旋转k个位置时πk的循环节的个数为Gcd(n,k),且每个循环的长度为L=n/gcd(n,i)。
http://poj.org/problem?id=2409
赤裸裸的Polya啊,数据范围很小,直接枚举i,对于旋转每个转换的循环节长度为gcd(i,n),对于翻转,如果为奇数,则全为n/2+1,否则一半为n/2+1,一半为n/2。第一个代码,好多地方写得好矬,在翻转的时候,循环是多余的。
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #define LL long long
- using namespace std;
- LL gcd(LL a,LL b){
- return b==0?a:gcd(b,a%b);
- }
- int c,s;
- LL Pow(int a,int b){
- return b==0?1:Pow(a,b-1)*a;
- }
- LL polya(){
- LL sum=0;
- for(int i=1;i<=s;i++)
- sum+=Pow(c,gcd(s,i));
- if(s&1)
- sum+=s*Pow(c,(s>>1)+1);
- else{
- for(int i=1;i<=s;i++)
- if(i&1)
- sum+=Pow(c,(s>>1)+1);
- else
- sum+=Pow(c,s>>1);
- }
- return sum/2/s;
- }
- int main(){
- while(scanf("%d%d",&c,&s)!=EOF&&c+s)
- printf("%I64d\n",polya());
- return 0;
- }
http://poj.org/problem?id=1286
也是模板题,同样需要考虑翻转和旋转,不过尝试加了欧拉函数优化,之前一直是枚举i,gcd(i,n)为循环个数,而每个循环长度为L=n/gcd(i,n),我们可以直接枚举L,这个黑书上
有证明,弱菜一下子说不清楚。
- #include<iostream>
- #include<cstdio>
- #include<cmath>
- #include<cstring>
- #define LL long long
- using namespace std;
- LL gcd(LL a,LL b){
- return b==0?a:gcd(b,a%b);
- }
- int c,s;
- LL eular(int n){
- LL sum=1;
- for(int i=2;i<=sqrt(1.0+n);i++)
- if(n%i==0){
- sum*=(i-1);
- n/=i;
- while(n%i==0){
- sum*=i;
- n/=i;
- }
- }
- if(n>1)
- sum*=(n-1);
- return sum;
- }
- LL Pow(int a,int b){
- LL ret=1;
- while(b){
- if(b&1)
- ret=ret*a;
- a=a*a;
- b>>=1;
- }
- return ret;
- }
- LL polya(){
- LL sum=0;
- for(int i=1;i<=s;i++)
- if(s%i==0)
- sum+=Pow(c,i)*eular(s/i);
- if(s&1)
- sum+=s*Pow(c,(s>>1)+1);
- else
- sum+=s/2*(Pow(c,(s>>1)+1)+Pow(c,s>>1));
- return sum/2/s;
- }
- int main(){
- c=3;
- while(scanf("%d",&s)!=EOF&&s!=-1){
- if(s==0) printf("0\n");
- else
- printf("%I64d\n",polya());
- }
- return 0;
- }
http://acm.hdu.edu.cn/showproblem.php?pid=3923
继续很水的一题,暴力搞搞就行。
- #include<iostream>
- #include<cstring>
- #include<cstdio>
- #include<cmath>
- #define MOD 1000000007
- #define LL long long
- using namespace std;
- int prime[30]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,
- 47,53,59,61,67,71,73,79,83,89,97,101},cnt=25;
- LL n,m;
- LL eular(LL num){
- LL ret=1;
- for(int i=0;i<cnt&&prime[i]<=num;i++)
- if(num%prime[i]==0){
- ret*=(prime[i]-1);
- num/=prime[i];
- while(num%prime[i]==0){
- num/=prime[i];
- ret*=prime[i];
- }
- }
- if(num>1)
- ret*=(num-1);
- return ret%MOD;
- }
- LL Pow(LL a,LL b){
- LL ret=1;
- while(b){
- if(b&1)
- ret=(ret*a)%MOD;
- a=(a*a)%MOD;
- b>>=1;
- }
- return ret;
- }
- LL Polya(){
- LL sum=0,i;
- for(i=1;i*i<n;i++){
- if(n%i==0){
- sum=(sum+eular(i)*Pow(m,n/i))%MOD;
- sum=(sum+eular(n/i)*Pow(m,i))%MOD;
- }
- }
- if(i*i==n)
- sum=(sum+eular(i)*Pow(m,i))%MOD;
- if(n&1)
- sum=(sum+n*Pow(m,n/2+1))%MOD;
- else
- sum=(sum+n/2*(Pow(m,n/2)+Pow(m,n/2+1)))%MOD;
- return (sum*Pow(2*n,MOD-2))%MOD;
- }
- int main(){
- LL t,cas=0;
- scanf("%I64d",&t);
- while(t--){
- scanf("%I64d%I64d",&m,&n);
- printf("Case #%I64d: %I64d\n",++cas,Polya());
- }
- return 0;
- }
http://poj.org/problem?id=2154
题目类似,但是数据很大,如果暴力枚举i肯定不行,必须使用欧拉函数优化,搜索n的因子
- #include<iostream>
- #include<cstdio>
- #include<cmath>
- #include<cstring>
- #define LL long long
- #define N 1000000000
- using namespace std;
- int n,p;
- bool flag[40000]={0};
- int prime[40000],cnt=0;
- void Prime(){
- for(int i=2;i<=sqrt(N+1.0);i++){
- if(flag[i]) continue;
- prime[cnt++]=i;
- for(int j=2;j*i<=sqrt(N+1.0);j++)
- flag[i*j]=true;
- }
- }
- LL gcd(LL a,LL b){
- return b==0?a:gcd(b,a%b);
- }
- int eular(int m){
- int sum=1;
- for(int i=0;i<cnt&&prime[i]<=m;i++)
- if(m%prime[i]==0){
- sum*=(prime[i]-1);
- m/=prime[i];
- while(m%prime[i]==0){
- sum*=prime[i];
- m/=prime[i];
- }
- }
- if(m>1)
- sum*=(m-1);
- return sum%p;
- }
- int Pow(int a,int b){
- int ret=1;
- a=a%p;
- while(b){
- if(b&1)
- ret=(ret*a)%p;
- a=(a*a)%p;
- b>>=1;
- }
- return ret;
- }
- int polya(){
- int sum=0;
- int i=1;
- for(;i*i<n;i++)
- if(n%i==0)
- sum=(sum+eular(i)*Pow(n,n/i-1)+eular(n/i)*Pow(n,i-1))%p;
- if(i*i==n)
- sum=(sum+eular(i)*Pow(n,i-1))%p;
- return sum;
- }
- int main(){
- int t;
- Prime();
- scanf("%d",&t);
- while(t--){
- scanf("%d%d",&n,&p);
- printf("%d\n",polya());
- }
- return 0;
- }
http://acm.hdu.edu.cn/showproblem.php?pid=1812
还是典型的Polya,推出每种情况就行了。不过需要用大数。
旋转只有 0,90,180,270度三种旋法。
旋0度,则置换的轮换数为n*n
旋90度,n为偶数时,则置换的轮换数为n*n/4,n为奇数,则置换的轮换数为(n*n-1)/4+1
旋180度,n为偶数时,则置换的轮换数为n*n/2,n为奇数,则置换的轮换数为(n*n-1)/2+1
旋270度,n为偶数时,则置换的轮换数为n*n/4,n为奇数,则置换的轮换数为(n*n-1)/4+1
反射 沿对角反射两种,沿对边中点连线反射两种
n为偶数时,沿对边中点连线反射两种的置换轮换数为 n*n/2
沿对角反射两种的置换轮换数为 (n*n-n)/2+n
n为奇数时,沿对边中点连线反射两种的置换轮换数为 (n*n-n)/2+n
沿对角反射两种的置换轮换数为 (n*n-n)/2+n
综上所述:用m种颜色给n*n矩形染色的方案数L
S=m^(n*n)+m^((n*n+3)/4)+m((n*n+1)/2)+m^((n*n+3)/4) (考虑旋转时)
n为奇数L= (S+4*m^((n*n+n)/2)) / 8
n为偶数L= (S+2*m^((n*n+n)/2)+2*m(n*n/2) )/ 8
http://acm.hdu.edu.cn/showproblem.php?pid=3547
对正方体的8个顶点染色,24种情况,理清楚就没问题了
最后的结果是ans=17*x^2+6*x^4+x^8
不过也得用大数,不会JAVA的伤不起呐
http://poj.org/problem?id=2888
这个和之前的不一样,给出K组限制关系,a和b不能相邻,借鉴10个经典矩阵问题,构造矩阵,快速幂乘,由于是一个循环,比如说最开始是a->b->->c->d->a,最后还是要回到a的,所以最终的解是矩阵的对角和。经典题目啊,赞一个
- <pre style="font-family:Courier New;text-align:left;"><span style="color:green;">
-
-
-
-
- </span></pre><pre name="code" class="html">#include<iostream>
- #include<cstdio>
- #include<cmath>
- #include<cstring>
- #define LL long long
- #define N 1000000000
- #define MOD 9973
- using namespace std;
- int n,m,p,k;
- bool flag[40000]={0};
- int prime[40000],cnt=0;
- struct matrix{
- int m[15][15];
- }mat;
- matrix operator*(matrix m1,matrix m2){
- matrix ans;
- for(int i=1;i<=m;i++)
- for(int j=1;j<=m;j++){
- ans.m[i][j]=0;
- for(int k=1;k<=m;k++)
- ans.m[i][j]=(ans.m[i][j]+m1.m[i][k]*m2.m[k][j])%MOD;
- }
- return ans;
- }
- matrix operator^(matrix m1,int num){
- matrix ans;
- memset(ans.m,0,sizeof(ans.m));
- for(int i=1;i<=m;i++) ans.m[i][i]=1;
- while(num){
- if(num&1) ans=ans*m1;
- m1=m1*m1;
- num>>=1;
- }
- return ans;
- }
- void Prime(){
- for(int i=2;i<=sqrt(N+1.0);i++){
- if(flag[i]) continue;
- prime[cnt++]=i;
- for(int j=2;j*i<=sqrt(N+1.0);j++)
- flag[i*j]=true;
- }
- }
- int eular(int m){
- int sum=1;
- for(int i=0;i<cnt&&prime[i]<=m;i++)
- if(m%prime[i]==0){
- sum*=(prime[i]-1);
- m/=prime[i];
- while(m%prime[i]==0){
- sum*=prime[i];
- m/=prime[i];
- }
- }
- if(m>1)
- sum*=(m-1);
- return sum%MOD;
- }
- int Pow(int a,int b){
- int ret=1;
- a=a%MOD;
- while(b){
- if(b&1)
- ret=(ret*a)%MOD;
- a=(a*a)%MOD;
- b>>=1;
- }
- return ret;
- }
- void debug(matrix t){
- for(int i=1;i<=m;i++){
- for(int j=1;j<m;j++)
- printf("%d ",t.m[i][j]);
- printf("%d\n",t.m[i][m]);
- }
- }
- int slove(int num){
- matrix temp=mat^num;
- int ret=0;
- for(int i=1;i<=m;i++)
- ret=(ret+temp.m[i][i])%MOD;
- return ret;
- }
- int polya(){
- int sum=0;
- int i=1;
- for(;i*i<n;i++)
- if(n%i==0)
- sum=(sum+eular(i)*slove(n/i)+eular(n/i)*slove(i))%MOD;
- if(i*i==n)
- sum=(sum+eular(i)*slove(i))%MOD;
- return (sum*Pow(n%MOD,MOD-2))%MOD;
- }
- int main(){
- int t;
- Prime();
- scanf("%d",&t);
- while(t--){
- scanf("%d%d%d",&n,&m,&k);
- for(int i=1;i<=m;i++)
- for(int j=1;j<=m;j++)
- mat.m[i][j]=1;
- while(k--){
- int a,b;
- scanf("%d%d",&a,&b);
- mat.m[a][b]=mat.m[b][a]=0;
- }
- printf("%d\n",polya());
- }
- return 0;
- }</pre><br>
- <br>
- <br>
- <a href="http://acm.hdu.edu.cn/showproblem.php?pid=2865">http://acm.hdu.edu.cn/showproblem.php?pid=2865</a>
- <p>AC神的题目?看上去和上一题类似,可是这里的颜色数量好多呐,不可能用矩阵了,但是考虑到矩阵的对角线全为0,其余全为1,可以推导出公式。</p>
- <pre name="code" class="cpp">#include<iostream>
- #include<cstdio>
- #include<cmath>
- #include<cstring>
- #define LL long long
- #define N 1000000000
- #define MOD 1000000007
- using namespace std;
- LL n,m,k;
- bool flag[40000]={0};
- int prime[40000],cnt=0;
- void Prime(){
- for(int i=2;i<=sqrt(N+1.0);i++){
- if(flag[i]) continue;
- prime[cnt++]=i;
- for(int j=2;j*i<=sqrt(N+1.0);j++)
- flag[i*j]=true;
- }
- }
- int eular(int m){
- int sum=1;
- for(int i=0;i<cnt&&prime[i]<=m;i++)
- if(m%prime[i]==0){
- sum*=(prime[i]-1);
- m/=prime[i];
- while(m%prime[i]==0){
- sum*=prime[i];
- m/=prime[i];
- }
- }
- if(m>1)
- sum*=(m-1);
- return sum%MOD;
- }
- LL Pow(LL a,LL b){
- LL ret=1;
- a=a%MOD;
- while(b){
- if(b&1)
- ret=(ret*a)%MOD;
- a=(a*a)%MOD;
- b>>=1;
- }
- return ret;
- }
- LL slove(LL p,LL k){
- LL ans=Pow(p-1,k);
- if(k&1)
- ans=(ans+MOD-(p-1))%MOD;
- else
- ans=(ans+p-1)%MOD;
- return ans;
-
- }
- int polya(){
- int sum=0;
- int i=1;
- for(;i*i<n;i++)
- if(n%i==0)
- sum=(sum+eular(i)*slove(m-1,n/i)+eular(n/i)*slove(m-1,i))%MOD;
- if(i*i==n)
- sum=(sum+eular(i)*slove(m-1,i))%MOD;
- return (sum*Pow(n%MOD,MOD-2))%MOD;
- }
- int main(){
- Prime();
- while(scanf("%I64d%I64d",&n,&m)!=EOF)
- printf("%d\n",(m*polya())%MOD);
- return 0;
- }</pre><br>
- <a href="http://acm.hdu.edu.cn/showproblem.php?pid=3441">http://acm.hdu.edu.cn/showproblem.php?pid=3441</a>
- <p>传说中的AC的“身体”,果断有待研究<a href="http://acm.hdu.edu.cn/showproblem.php?pid=2481"><br>
- </a></p>
- <p><a href="http://acm.hdu.edu.cn/showproblem.php?pid=2481">http://acm.hdu.edu.cn/showproblem.php?pid=2481</a></p>
- <p>08年成都的题目,有大神会记得教我<br>
- </p>
- <p><br>
- </p>
- <br>
- <pre></pre>
-
话说
ICPC
的题目是越来越难,因为经典的算法大家都知道了,因此出题的方向只能是要么把模型隐藏的很深,要么就把一系列算法知识综合起来考察,这个时候分析问题的能力和灵活运用知识的能力就显得尤为重要。
polya
定理在很久以前的ICPC题目中就已经出现过,不过那个时候大家对于置换群都了解不多,因此polya定理算是很生僻的一个东西。然而人类总是飞速的进步,现在互联网上铺天盖地的题解使得polya定理走出深闺,逐渐被广大acmer所熟知。但是魔高一尺道高一丈,出题人也逐渐把polya定理的题出得越来越难做,越来越不好想,今天我就来总结一下这些颇有难度的polya定理题目(包括Burnside引理)。
polya
定理主要就是解决一类着色问题,或者说是同构计数问题。设染色方案数是n,置换群个数是p,置换群长度是s,那么利用Burnside引理,通过考察每个染色方案和每个置换群,可以在O(nsp)时间复杂度计算出答案。polya定理巧妙的利用同一循环内着色相同这个事实,避开了枚举着色方案,使得复杂度降到了O(sp)。但是利用polya定理的条件就是对于染色没有限制,如果不满足这个条件(比如对于不同循环节的染色限制,颜色数的限制等等),就没法直接使用polya定理。另一方面,同构计数无论如何也无法避开找置换群,因此很多题目也在这个上面做文章,把置换群弄得非常多,使得常规的枚举无法满足要求,必须寻求优化解法。
这样讨论下来就可以把polya
定理分成几个等级,最简单的就是找出置换群,染色就行了,这类代表题目有:
HOJ 2084 The Colored Cubes
HOJ 2647 Megaminx
POJ 1286 Necklace of Beads
POJ 2409 Let it Bead
HDU 1812 Count the Tetris
这些题目都是polya
定理最基本的应用,当然以后估计再也见不到这样的题目了,因为太赤裸裸了。
第二个等级的题目难度就略微提升了一些,比如加入颜色限制,这样就要用Burnside
引理:
TJU 2795 The Queen's New Necklaces
这个题目就是对每种颜色的个数进行了限制,不过因为置换群很有规律,我用的是多重集排列来计数的。
UVa 10601 Cubes
也是限制了颜色数,但是这个题里置换群比较没规律,我是直接搜的。
UVa 11255 Necklace
和上面两个题目类似,我也是用排列组合直接数了,写起来有些麻烦,也许用dp
更简单些。
POJ 2154 Color
这个题目是一个里程碑,它标志着一类题目的优化方法。同样是项链旋转,因为置换群数量太多,利用数论的知识优化。
第三个等级就比较变态了,几乎没有一看就能想出来的题目。这类题目的特点是,非常综合,用到了很多知识;有的题目难点甚至不在求解同构计数的部分。
POJ 2888 Magic Bracelet
难度指数:★★★
一道很不错的题目,这里加入连接限制同时还考察优化,优化方法同上。连接限制如何处理?注意到项链个数很少,因此可以建图,然后分别求出每种颜色连接n
个珠子后回到自身的方案数,累加即可,这里可以用矩阵快速幂求解。
TJU 3352 Birthday Toy
难度指数:★★★
这个题目出得也很不错,同样是加入链接限制,不过这里的连接限制很有规律性,是相邻的珠子不可以染成同色,因此可以列出递推关系,最后化简成公式求解。此外由于还是项链旋转,用到了一样的优化方式(
这个优化方式已经被考烂了)。
HDU 2481 Toy
难度指数:★★★★
08
年成都现场赛题目,难点在于求解递推关系。这个题目比较新颖的地方在于不是染色了,而是同构图计数。首先由于图形的特殊性可以推出递推关系(不是很好想),然后利用矩阵求解。此外这个题目把同一循环节的缩成了一个珠子,利用这用方式来考虑问题,是一种新的思路。此外由于还是项链旋转,用到了一样的优化方式(凡是跟项链有关的就没有不考这个的了)。这个题目详尽的题解在这里:http://hi.baidu.com/spellbreaker/blog/item/1a7d9902ff6844e409fa93fb.html
SGU 282 Isomorphism
难度指数:★★★★★
这个题目比较新颖,置换非常多,因此需要一些巧妙的方法优化。置换的个数达到了n!
,但是可以通过搜索来枚举每个循环节的长度,把复杂度降到了求解n的分拆数方案数那么多。设n = L1 + L2 + ... + Lm,那么满足这个类型的置换个数是n!/ (L1 * L2 * ... * Lm * k1! * k2! * ... * kt!),其中t是不同的循环节长度的个数,ki是每种循环节长度,这个公式的大体思想就是:首先因为是置换,因此每个循环节内的数是固定的,把一个循环节内的数看成1个就变成了多重集的排列,但是每个循环节(Li)内,第一个数有(Li - 1)种选法(只要不是自己就行),第二个数有(Li - 2)种选法,依次类推,因此每个循环节还要乘以(Li - 1)!;之后因为对于两个同样长度的循环节,一旦选定了位置,其实不可以互换,因此要把多余的方案除掉,最后就是上述的公式。找出了置换的个数,接下来的问题就是,因为题目是对边染色,因此要把点置换映射成边置换。通过小范围的观察可以发现(具体证明不太会):一个长度为L的点循环节对应[L/2]个边循环节,两个长度分别是L1、L2的点循环节对应gcd(L1,L2)个边循环节。因为polya定理同一循环节内染色相同,因此不必关心对应后的边循环节长度,只要知道个数即可(这一点很巧妙),所以这样映射便可以求出结果。最后要注意的是题目说明了模一个素数,因此可以预处理出逆元来。
SPOJ 422 Transposing is Even More Fun
难度指数:★★★★★
这个题目需要一些观察力。把地址转置之后对应的写下来,会发现,一个长度为L
循环节只需要移动L-1次(利用一个元素进行对换),这样假设有k个循环节,那么答案就是2 ^ (a + b) - k,关键问题是如何求k。循环节的个数其实就是相当于地址右移若干个b位后本质不同的地址个数,这样就划归到了polya定理的范畴。长度为a+b的地址一共可以右移(a + b) / (a, b)次(之后就出现循环了),因此这就是置换的个数。现在分别考虑每个置换下不同的地址个数,设g =gcd(a, b),那么可以看成一共有(a + b) / g个珠子,每个大小是2 ^ g,这样如果移动i下,那么对应的本质不同的地址个数是(2 ^ g) ^ gcd(i, (a+ b) / g)多个(类似于项链旋转),最后累加然后除以总置换数即可。
然后的问题就是如何高效求解,由于数据组数非常多,利用欧拉函数以O(sqrt(a + b))
的复杂度依然TLE。后来参照cyx的论文,实现了一个理论复杂度看似很高但是实际很快的方法。记f[i]表示满足gcd(i, (a + b) / g)是i的个数,先把总数分配给1,然后对(a + b) / g因式分解,用类似bfs的方法,扩展当前状态,如果从x扩展到了xp,那么就把x总数的1/p分给xp,注意不要重复扩展,利用一个单调队列让每个和数都唯一的被它的最小素因子扩展一次即可。这个方法复杂度难以估计但是很快出解。