【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
posted @ 2018-01-07 08:47  CQzhangyu  阅读(302)  评论(0编辑  收藏  举报