【SPOJ419】Transposing is Fun Pólya定理+欧拉函数
【SPOJ419】Transposing is Fun
题意:给你一个\(2^a\times2^b\)的矩阵,将\(1...n\)中的数依次从左到右,从上往下填到矩阵里,再把矩阵转置,然后把所有数从左到右,从上往下拿出来得到一个新的排列\(A\)。你现在每次可以交换两个数,问你从\(1...n\)变成排列\(A\)最少要进行多少次操作。
询问次数\(\le400000,a+b\le 10^6\)
题解:首先我们可以找到所有的循环节,如果一个循环节中有\(x\)个数,需要交换\(x-1\)次。所以我们只需要求出循环节的个数\(k\),那么答案就是\(2^{a+b}-k\)。
如何求出循环节的个数呢?假设\(a=5,b=3\),考虑元素\((12,1)\),其二进制表示为\((01010,001)\),它原来的位置是\(01010\ 001\),新位置是\(001\ 01010\)。相当于将每个数的位置二进制向右移动\(b\)位。
所以,我们可以令\(gcd(a,b)=g\),将\(g\)个数分成一组,相当于用\(2^g\)种颜色去染\(a+b\over g\)个珠子,就又变成了POJ2154。
#include <cstring>
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll P=1000003;
const int N=2000000;
int T,a,b,n,m,g,num;
ll ans;
ll pw[N+10];
int cnt[100],p[100];
int phi[N+10],np[N+10],pri[N],xp[N+10],mn[N+10];
inline ll pm(ll x,int y)
{
ll z=1;
while(y)
{
if(y&1) z=z*x%P;
x=x*x%P,y>>=1;
}
return z;
}
int gcd(int a,int b)
{
return (!b)?a:gcd(b,a%b);
}
void dfs(int x,int d)
{
if(x==m+1)
{
ans=(ans+pw[g*d]*phi[n/d])%P;
return ;
}
for(int i=0;i<=cnt[x];i++,d*=p[x]) dfs(x+1,d);
}
inline void init()
{
phi[1]=1;
int i,j,p;
for(pw[0]=i=1;i<=N;i++) pw[i]=(pw[i-1]<<1)%P;
for(i=2;i<=N;i++)
{
if(!np[i]) pri[++num]=i,mn[i]=i,xp[i]=1,phi[i]=i-1;
for(j=1;j<=num&&i*pri[j]<=N;j++)
{
p=pri[j],np[i*p]=1,mn[i*p]=p;
if(i%p==0)
{
phi[i*p]=phi[i]*p,xp[i*p]=xp[i]+1;
break;
}
phi[i*p]=phi[i]*(p-1),xp[i*p]=1;
}
}
}
inline void work()
{
scanf("%d%d",&a,&b),ans=m=0;
if(!a||!b)
{
puts("0");
return ;
}
g=gcd(a,b),n=(a+b)/g;
int t=n;
while(t!=1)
{
p[++m]=mn[t],cnt[m]=xp[t];
while(mn[t]==p[m]) t/=p[m];
}
dfs(1,1);
printf("%lld\n",(pw[a+b]-ans*pm(n,P-2)%P+P)%P);
}
int main()
{
init();
scanf("%d",&T);
while(T--) work();
return 0;
}//1 2 30000
| 欢迎来原网站坐坐! >原文链接<