Burnside
分析
\[S=\frac{\sum\text{每种方案对应的不动点数目}}{\text{方案}}(\text{包括不变的情况})
\]
群论中的内容(方案可能需要拆成置换)
注意,如果有0需要特判掉(除0)
例题1
旋转等价
假设原来是
\[<1,2,3,4,\cdots ,n>
\]
旋转\(k\)后变成
\[<1+k,2+k,\cdots,n+k>(\% n)
\]
则:
\[1=1+k=1+k+k=\cdots=1+ck(\%n),ck=0(\%n)
\]
此时:
\[c_{min}=\frac{n}{(n,k)}
\]
也就是说旋转\(k\)后,对于每个不动点,每种元素都有\(\frac{n}{(n,k)}\)个相同,也就是总共有\((n,k)\)个选择的可能
故
\[\begin{aligned}
S=&\sum_{i=1}^{n}p^{(n,i)}\\=&\sum_{d|n}p^d\times \phi(\frac{n}{d})
\end{aligned}
\]
时间复杂度\(O(\sqrt n lg n)\)
注意,如果有0需要特判掉(除0)
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5,M=1e6+5;
int n,p,cnt,pri[M],phi[N];
bool fl[N];
inline int ksm(int a,int b) {
int ret=1;
while(b) {
if(b&1) ret=ret*a%p;
a=a*a%p,b>>=1;
}
return ret;
}
inline int Phi(int x) {
if(x<=1e7) return phi[x];
int tt; int ret=1;
for(int j=1;pri[j]<=sqrt(x);j++) {
if(x%pri[j]==0) {
tt=pri[j]-1; x/=pri[j];
for(;x%pri[j]==0;x/=pri[j]) tt*=pri[j];
ret*=tt;
if(x<=1e7) return phi[x]*ret;
}
}
if(x>1) ret*=(x-1);
return ret;
}
int main() {
int T; scanf("%d",&T);
phi[1]=1;
for(int i=2;i<=1e7;i++) {
if(!fl[i]) {
pri[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt&&pri[j]*i<=1e7;j++) {
fl[i*pri[j]]=1;
if(i%pri[j]==0) {
phi[i*pri[j]]=phi[i]*pri[j];
break;
} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
while(T--) {
scanf("%d%d",&n,&p);
int ans=0;
for(int i=1;i<=sqrt(n);i++) {
if(n%i==0) {
ans=(ans+ksm(n%p,i-1)*(Phi(n/i)%p))%p;
if(i*i!=n) ans=(ans+ksm(n%p,n/i-1)*(Phi(i)%p))%p;
}
}
printf("%d\n",ans);
}
return 0;
}
例题2
翻转重合,见小本本,奇偶分类
注意,如果有0需要特判掉(除0)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int m,n;
int gcd(int x,int y) {
return !y?x:gcd(y,x%y);
}
ll ksm(int a,int b) {
ll ret=1;
for(int i=1;i<=b;i++) ret*=a;
return ret;
}
int main() {
while(scanf("%d%d",&m,&n),n!=0||m!=0) {
ll ans=0;
for(int i=1;i<=n;i++) {
ans+=ksm(m,gcd(i,n));
}
if(n%2==0) ans+=(ksm(m,n/2+1)+ksm(m,n/2))*(n/2);
else ans+=ksm(m,(n-1)/2+1)*n;
printf("%lld\n",ans/2/n);
}
return 0;
}
例题3
旋转方式是点的全排列,求不动点
发现边可以分成两部分:在置换中的边和两个置换中的边,分类讨论求解即可
而置换只需要知道置换的个数和置换的大小即可知道答案,所以对\(n\)自然数拆分组合数计算即可
推导部分见小本本
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+5;
int jc[N],inv[N],ans,g[60][60],n,m,p,a[N],mi[N];
void dfs(int t,int res,int lst,int num,int s) {
if(!res) {
s=(ll)s*jc[num]%p;
ans=(ans+s)%p;
return;
}
if(lst>res) return;
int tt=0;
if(lst) {
for(int j=1;j<t;j++) tt+=g[a[j]][lst];
a[t]=lst;
dfs(t+1,res-lst,lst,num+1,(ll)s*mi[(lst>>1)+tt]%p*inv[lst]%p);
}
for(int i=lst+1;i<=res;i++) {
tt=0;
for(int j=1;j<t;j++) {
tt+=g[a[j]][i];
}
a[t]=i;
dfs(t+1,res-i,i,1,(ll)s*mi[(i>>1)+tt]%p*inv[i]%p*jc[num]%p);
}
}
int gcd(int x,int y) {
return !y?x:gcd(y,x%y);
}
int main() {
scanf("%d%d%d",&n,&m,&p);
inv[0]=inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=(ll)(p-p/i)*inv[p%i]%p;
jc[0]=jc[1]=1;
for(int i=2;i<=n;i++) jc[i]=(ll)jc[i-1]*inv[i]%p;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) g[i][j]=gcd(i,j);
}
mi[0]=1;
for(int i=1;i<=1e6;i++) {
mi[i]=(ll)mi[i-1]*m%p;
}
dfs(1,n,0,0,1);
printf("%d\n",ans);
return 0;
}
买一送一:
将有无看作01状态
例题4
注意洗牌法需要加入自己洗成自己
然后并查集搞搞DP求不动点
#include<cstdio>
#define ll long long
using namespace std;
const int N=65;
int ca,cb,cc,n,m,p,c,fa[N],num[N],w[N],f[N][N][N];
ll ans,s;
int ksm(ll a,int b) {
ll ret=1;
while(b) {
if(b&1) ret=ret*a%p;
a=a*a%p; b>>=1;
}
return (int)ret;
}
int find(int x) {
return x==fa[x]?x:fa[x]=find(fa[x]);
}
inline int ex(int x) {
return x>=p?x-p:x;
}
int main() {
scanf("%d%d%d%d%d",&ca,&cb,&cc,&m,&p);
n=ca+cb+cc;
s=ans=1;
for(int i=2;i<=n;i++) {
s=s*i%p;
if(i==ca) {
ans=ans*ksm(s,p-2)%p;
}
if(i==cb) {
ans=ans*ksm(s,p-2)%p;
}
if(i==cc) {
ans=ans*ksm(s,p-2)%p;
}
}
ans=ans*s%p;
for(int e=1;e<=m;e++) {
for(int i=1;i<=n;i++) {
fa[i]=i,num[i]=1;
}
for(int i=1;i<=n;i++) {
int x; scanf("%d",&x);
int u=find(x),v=find(i);
if(u!=v) {
fa[u]=v,num[v]++;
}
}
c=0;
for(int i=0;i<=ca;i++) {
for(int j=0;j<=cb;j++) {
for(int k=0;k<=cc;k++) {
f[i][j][k]=0;
}
}
}
f[0][0][0]=1;
for(int i=1;i<=n;i++) {
if(find(i)==i) {
for(int j=ca;j>=0;j--) {
for(int k=cb;k>=0;k--) {
for(int l=cc;l>=0;l--) {
if(j>=num[i]) {
f[j][k][l]=ex(f[j][k][l]+f[j-num[i]][k][l]);
}
if(k>=num[i]) {
f[j][k][l]=ex(f[j][k][l]+f[j][k-num[i]][l]);
}
if(l>=num[i]) {
f[j][k][l]=ex(f[j][k][l]+f[j][k][l-num[i]]);
}
}
}
}
}
}
ans=ex(ans+f[ca][cb][cc]);
}
printf("%lld\n",ans*ksm(m+1,p-2)%p);
return 0;
}
例题5
旋转的限制可以通过burnside解决
不妨设\(f[d]\)表示环大小为\(d\)的答案
\[S=\sum_{d|n}f[d]\times \phi(\frac{n}{d})
\]
推导见小本本
至于\(f[d]\),可以想想DP,发现可以矩阵优化
\[f[d]=g^d对角线的和,g表示的是矩阵
\]
#include<bits/stdc++.h>
const int p=9973;
using namespace std;
const int N=11,M=1e7+5,nn=1e6+5;
int cnt,pri[nn],phi[M],n,m,K;
bool fl[M];
struct A{
int a[N][N];
}a,ret,g;
inline int Phi(int x) {
if(x<=1e7) return phi[x];
int tt; int ret=1;
for(int j=1;pri[j]<=sqrt(x);j++) {
if(x%pri[j]==0) {
tt=pri[j]-1; x/=pri[j];
for(;x%pri[j]==0;x/=pri[j]) tt*=pri[j];
ret*=tt;
if(x<=1e7) return phi[x]*ret;
}
}
if(x>1) ret*=(x-1);
return ret;
}
inline int inv(int a,int b) {
int ret=1;
while(b) {
if(b&1) ret=ret*a%p;
a=a*a%p,b>>=1;
}
return ret;
}
inline A operator *(A x, A y) {
A z;
for(int i=1;i<=m;i++) {
for(int j=1;j<=m;j++) {
z.a[i][j]=0;
for(int k=1;k<=m;k++) {
z.a[i][j]=(z.a[i][j]+x.a[i][k]*y.a[k][j])%p;
}
}
}
return z;
}
inline int ksm(int b) {
for(int i=1;i<=m;i++) {
for(int j=1;j<=m;j++) {
ret.a[i][j]=i==j;
}
}
a=g;
while(b) {
if(b&1) ret=ret*a;
a=a*a; b>>=1;
}
int ans=0;
for(int i=1;i<=m;i++) {
ans=(ans+ret.a[i][i])%p;
}
return ans;
}
int main() {
int T; scanf("%d",&T);
phi[1]=1;
for(int i=2;i<=1e7;i++) {
if(!fl[i]) {
pri[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt&&pri[j]*i<=1e7;j++) {
fl[i*pri[j]]=1;
if(i%pri[j]==0) {
phi[i*pri[j]]=phi[i]*pri[j];
break;
} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
while(T--) {
scanf("%d%d%d",&n,&m,&K);
for(int i=1;i<=m;i++) {
for(int j=1;j<=m;j++) g.a[i][j]=1;
}
for(int i=1;i<=K;i++) {
int u,v; scanf("%d%d",&u,&v);
g.a[u][v]=g.a[v][u]=0;
}
int ans=0;
for(int i=1;i<=sqrt(n);i++) {
if(n%i==0) {
ans=(ans+ksm(i)*(Phi(n/i)%p))%p;
// printf("%d %d %d\n",i,ksm(i),Phi(n/i));
if(i*i!=n) {
ans=(ans+ksm(n/i)*(Phi(i)%p))%p;
// printf("%d %d %d\n",n/i,ksm(n/i),Phi(i));
}
}
}
ans=ans*inv(n%p,p-2)%p;
printf("%d\n",ans);
}
return 0;
}
例题5
同样,只需要改变f即可
按环处理
不妨设\(f[i][j]\)表示\(i\)个人,头一定是男生,末尾\(j\)个女生的方案,然后枚举头即可
记得一定要去掉全女生的不动点方案,最后特判是否需要加上,因为\(n\)个女生很有可能不合法
前缀和优化即可
WA了一次:忘记去掉女生的方案
#include<bits/stdc++.h>
#define ll long long
const int p=1e8+7;;
using namespace std;
const int N=2005;
int cnt,pri[N],phi[N],n,m,inv[N],f[N][N];
bool fl[N];
inline int mo(int x) {
return x>=p?x-p:x;
}
inline int ksm(ll a,int b) {
ll ret=1;
while(b) {
if(b&1) ret=ret*a%p;
a=a*a%p,b>>=1;
}
return ret;
}
inline int ask(int n) {
if(m>=n) return ksm(2,n)-1;
int ret=0;
for(int k=0;k<=min(n-1,m);k++) {
ret=mo(ret+f[n-k][min(m-k,n-k-1)]);
}
return ret;
}
int main() {
int T; scanf("%d",&T);
phi[1]=1; inv[1]=1;
for(int i=2;i<=2000;i++) {
if(!fl[i]) {
pri[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt&&pri[j]*i<=2000;j++) {
fl[i*pri[j]]=1;
if(i%pri[j]==0) {
phi[i*pri[j]]=phi[i]*pri[j];
break;
} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
inv[i]=(ll)(p-p/i)*inv[p%i]%p;
}
while(T--) {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) f[1][i]=0;
f[1][0]=1;
for(int i=2;i<=n;i++) {
f[i][0]=f[i-1][0];
for(int j=1;j<=m;j++) {
f[i][j]=f[i-1][j-1];
f[i][0]=mo(f[i][0]+f[i-1][j]);
}
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
f[i][j]=mo(f[i][j]+f[i][j-1]);
}
}
ll ans=0;
for(int i=1;i<=sqrt(n);i++) {
if(n%i==0) {
ans=(ans+(ll)ask(i)*phi[n/i]%p)%p;
if(i*i!=n) {
ans=(ans+(ll)ask(n/i)*phi[i]%p)%p;
}
}
}
// printf("%d\n",ans);
ans=ans*inv[n]%p;
if(m>=n) ans++;
printf("%lld\n",(ans+p)%p);
}
return 0;
}