【POJ2888】Magic Bracelet-Burnside引理+数论+DP矩阵优化
测试地址:Magic Bracelet
题目大意:要用
做法:这题非常经典,思路很有代表性,是进阶Burnside引理和Polya定理题的一块敲门砖,需要用到:Burnside引理,欧拉函数,DP+矩阵快速幂,乘法逆元。
首先一看题目是计数,就自然而然地联想到Burnside引理和Polya定理,然而这题有不能相邻的条件,所以不能直接用Polya定理,那么我们考虑使用Burnside引理来解决。
我们知道环形的旋转置换有
但是最后一个元素的填色除了和它前一个元素有关,还和第一个元素有关,那么这个状态转移方程是不是错的呢?实际上,我们只需一开始枚举第一个元素的颜色
上面的方法的时间复杂度是
首先优化计算Burnside公式的时间复杂度。我们知道
接下来优化DP的时间复杂度,注意到
经过了以上两个优化,外层枚举的复杂度降到差不多
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,ans;
int T,m,k;
struct matrix {ll s[11][11];} M[40];
void exgcd(ll a,ll b,ll &x,ll &y)
{
ll x0=1,y0=0,x1=0,y1=1;
while(b)
{
ll tmp,q;
q=a/b;
tmp=x0,x0=x1,x1=tmp-q*x1;
tmp=y0,y0=y1,y1=tmp-q*y1;
tmp=a,a=b,b=tmp%b;
}
x=x0,y=y0;
}
matrix mult(matrix A,matrix B)
{
matrix S;
memset(S.s,0,sizeof(S.s));
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=m;k++)
S.s[i][j]=(S.s[i][j]+A.s[i][k]*B.s[k][j])%9973;
return S;
}
matrix power(ll x)
{
int i=0;
matrix S;
memset(S.s,0,sizeof(S.s));
for(int j=1;j<=m;j++) S.s[j][j]=1;
while(x)
{
if (x&1) S=mult(S,M[i]);
i++;x>>=1;
}
return S;
}
ll phi(ll x)
{
ll s=x;
for(ll i=2;i*i<=x;i++)
if (!(x%i))
{
s=s/i*(i-1);
while(!(x%i)) x/=i;
}
if (x>1) s=s/x*(x-1);
return s;
}
void solve(ll x)
{
matrix S=power(x);
ll sum=0;
for(int i=1;i<=m;i++)
sum=(sum+S.s[i][i])%9973;
sum=(sum*phi(n/x))%9973;
ans=(ans+sum)%9973;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lld%d%d",&n,&m,&k);
ans=0;
memset(M[0].s,0,sizeof(M[0].s));
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
M[0].s[i][j]=1;
for(int i=1,a,b;i<=k;i++)
{
scanf("%d%d",&a,&b);
M[0].s[a][b]=M[0].s[b][a]=0;
}
for(int i=1;i<=35;i++) M[i]=mult(M[i-1],M[i-1]);
for(ll i=1;i*i<=n;i++)
if (n%i==0)
{
solve(i);
if (i!=n/i) solve(n/i);
}
ll x0,y0;
exgcd(n,9973,x0,y0);
x0=(x0%9973+9973)%9973;
printf("%lld\n",(x0*ans)%9973);
}
return 0;
}