知識
首先群的概念極其重要,說白了就是一個封閉集合,
可以通過某種二元運算可以得到的元素都在這里面,
再其次就是子群,這個和自己完全不一樣,因為子群自己也是封閉的
所以這樣的話,一個群一定有兩個子群,一個單位元,一個就是自己
左右陪集的話,主要是二元運算符的運算順序會影響結果,
這個東西在阿貝爾群中,左右陪集是完全相同的,因為二元運算符滿足交換律。。。
還有置換,就是一個排列里面的數,一一對應另外一個排列里面的數,對應過去就是了
但是這樣的排列不一定是全排列,只要封閉就可以
置換群就是一堆不同的置換所組成的群
因為置換的過程是排列的一一對應,所以每一個置換必然能表示為一堆置換的加和
就是我們可以對置換進行拆分,而拆分出來的塊又是一個小的置換,但是數字不連續
因為可以被分出來,所以這些置換根據對應關系可以變成一個一個的環
比如說1->2,2->1,這樣1,2就組成了一個環,還可以連接更多的數,組成更長的環
然而這還不是重點,好博客
burnside引理
對於一個序列或者環求它在某個置換群的意義下的不同方案數
答案就是\(\displaystyle\frac{1}{|G|}\sum_{g\in G}M(g)\)
其中 G 是所有置換的集合,M(g) 是一個置換的不動點個數。
所謂不動點就是在當前的置換的條件下,換完之后每個點所對應的類型不變
個數就是這種方案的個數。。。。。
當然還有更加學術的表達,那個就太惡心了,所以我用了這種表達,學術的
這個一般是利用dp去求每一個置換中的不動點個數,所以這種題的難點不在引理
而是在如何求這個不動點個數。。。
拉格朗日定理簡單來說就是一個群的子群的大小是這個群的大小的約數
也就是子群的階整除當前群的階
后面證明就不會了
polya定理
這個極其簡單。。。。就是在burnside的基礎上簡化了計算不動點個數的方法
前面說到對於每一個置換都可以拆成一個一個的環,
那既然是不懂點,這些環中的每一個點的類型一定要相同才行,
不然置換之后就一定不相同,就不是不動點了。。
而polya定理就是針對這個來引入的一個定理
當這些環之間沒有限制的時候,我們一共有m中類型可以選擇
那么不動點的個數就是\(m^s\),s表示環的個數
每一個環內都有m種選擇,那么直接乘起來就是答案了
這個定理的應用范圍極其狹窄,所以要看好到底有沒有限制
題目
luogu polya大板子
就是裸的polya
然后不要直接去枚舉每一種置換,枚舉gcd,然后用\(\varphi\)來計數,簡單爆了\
code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const ll mod=1e9+7;
ll t,n,ans;
ll ksm(ll x,ll y){
ll ret=1;
while(y){
if(y&1)ret=ret*x%mod;
x=x*x%mod;
y>>=1;
}
return ret;
}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
ll phi(ll x){
ll ret=x;
for(re i=2;i*i<=x;i++){
if(x%i)continue;
ret=ret/i*(i-1);
while(x%i==0)x/=i;
}
if(x>1)ret=ret/x*(x-1);
return ret;
}
signed main(){
scanf("%lld",&t);
while(t--){
scanf("%lld",&n);ans=0;
for(re i=1;i*i<=n;i++){
if(n%i)continue;
ans=(ans+1ll*ksm(n,i)*phi(n/i)%mod)%mod;
if(i!=n/i)ans=(ans+1ll*ksm(n,n/i)*phi(i)%mod)%mod;
}
printf("%lld\n",ans*ksm(n,mod-2)%mod);
}
}
Cards
這個不能用polya,因為有數量限制,所以只能dp的去求不動點個數
循環節都給你了好吧,直接找,\(\mathcal{O(n)}\)的
因為是數量限制嘛,每個循環又有一個體積,所以就是三維的背包,挺簡單的
逆元就直接快速冪就好了
code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=25;
int sr,sb,sg,n,m,mod;
int f[N][N][N],ans;
int nex[N*3],fo[N*3],cnt;
bool vis[N*3];
int ksm(int x,int y){
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return ret;
}
int get_ans(){
memset(f,0,sizeof(f));
memset(vis,false,sizeof(vis));
f[0][0][0]=1;cnt=0;
for(re i=1;i<=n;i++){
if(!vis[i]){
int now=i,tmp=0;
while(!vis[now])
vis[now]=true,now=nex[now],tmp++;
fo[++cnt]=tmp;
}
}
//cout<<cnt<<" "<<fo[cnt]<<endl;
for(re i=1;i<=cnt;i++)
for(re ri=sr;~ri;ri--)
for(re bi=sb;~bi;bi--)
for(re gi=sg;~gi;gi--){
if(ri>=fo[i])f[ri][bi][gi]=(f[ri][bi][gi]+f[ri-fo[i]][bi][gi])%mod;
if(bi>=fo[i])f[ri][bi][gi]=(f[ri][bi][gi]+f[ri][bi-fo[i]][gi])%mod;
if(gi>=fo[i])f[ri][bi][gi]=(f[ri][bi][gi]+f[ri][bi][gi-fo[i]])%mod;
//cout<<ri<<" "<<bi<<" "<<gi<<" "<<f[ri][bi][gi]<<endl;
}
return f[sr][sb][sg];
}
signed main(){
scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&mod);
n=sr+sb+sg;
for(re i=1;i<=m;i++){
for(re j=1;j<=n;j++)scanf("%d",&nex[j]);
ans=(ans+get_ans())%mod;
}
for(re i=1;i<=n;i++)nex[i]=i;ans=(ans+get_ans())%mod;
printf("%lld",1ll*ans*ksm(m+1,mod-2)%mod);
}
周末晚會
這個限制只是對女生來說的,但也是有限制,直接考慮dp就行了
這個dp還是非常的難想,真的,我直接看題解去了。。。。拍一拍就過了
首先我們要知道,我們找到的循環個數就是d=gcd(n,i),所以整個環就是d的循環
所以我們只需要保證d的合法性,又因為d是循環的,所以首尾也要有限制
我們要構造一個dp,來做這個題,要保證長度為d的環上沒有連續的超過k個女生
我們設女生為0,男生為1,設dp[i][j]表示前j個數中有不超過i個連續的0,
啊,我錯了,在這個題中,i=k,然后第一維就不需要了
我們欽定第一個和最后一個都是1,中間的愛咋咋地,所以我們轉移的時候直接枚舉就行了
但是這好像是\(\mathcal{O(n^2)}\)的,但是你發現轉移的過程是連續的,前綴和優化
然后我們有了這個dp數組,我們求答案就很方便了
我們當前一共有d個位置,對於i個數來說,一共有d-i種放置的辦法,但是放之前,看看d-i是否>=k
然后就按照之前的套路轉移就好了。
所以這個題的模數是1e8+7,wocnmd
code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const ll mod=1e8+7;
const int N=2005;
ll T,n,k;
ll f[N],fro[N],ans;
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
ll ksm(ll x,ll y){
ll ret=1;
while(y){
if(y&1)ret=ret*x%mod;
x=x*x%mod;
y>>=1;
}
return ret;
}
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld%lld",&n,&k);
memset(f,0,sizeof(f));
f[1]=1;fro[1]=1;ans=0;
for(re i=2;i<=n;i++){
f[i]=(f[i]+fro[i-1]-fro[max(i-k-2,0ll)]+mod)%mod;
fro[i]=(fro[i-1]+f[i])%mod;
//cout<<f[i]<<" ";
}
//cout<<endl;
for(re i=0;i<n;i++){
ll d=gcd(n,i);
for(re j=max(d-k,1ll);j<=d;j++)
ans=(ans+f[j]*(d-j+1)%mod)%mod;
if(n<=k)ans=(ans+1)%mod;
}
printf("%lld\n",ans*ksm(n,mod-2)%mod);
}
}
有色圖
色圖到底在哪里????-----小粉兔
這個好難好難,我看題解看了1h30min,好好理解一下
首先題目中給的是關於點的置換,而我們要看的卻是邊的置換,如何做??
轉換唄,人家都給你完全圖了,給你多大的方便,你還不會???
我們設\(b_i\)為一個置換中點的循環長度,就是等價類的大小
那么所有的邊會被分成兩類,一類是兩個端點在同一個類中,一類是不同類中
對於在同一類中的(第一類),我們會發現,所有邊的等價類中的邊的長度一定相同,
所謂長度就是他連接的連接個端點之間跨越了幾個點,
那么每旋轉\(\lfloor\frac{b_i}{2}\rfloor\),就會回到原來的位置。這個可以分奇偶來討論
當\(b_i\)為奇數的時候,直接旋轉\(\frac{b_i-1}{2}\)就可以回到原來的地方
當\(b_i\)為偶數的時候,中間會多一次,上下正好反過來,就是\(\frac{b_i}{2}\)
對於在不同類當中的(第二類),兩兩組合,然后發現循環的長度(等價類的大小)就是\(lcm(b_i,b_j)\)
這樣的話我們對於每一個置換就有了每一個置換對應的答案
但是我們直接枚舉是不可能的
我們對每一個點分配等價類,那就是一個可重集排列\(\displaystyle\frac{n!}{\prod b_i!}\)
對於每一個等價類中,我們還有一個圓排列種方案\(\prod (b_i-1)!\),乘起來就是\(\displaystyle\frac{n!}{\prod b_i}\)
但是我們的等價類是沒有順序之分的,萬一有5個數分到了1,有5個數分到了2,這個是可以互換的,不能算作多種方案
所以我們計算每一種長度的個數為\(c_i\),表示在當前置換的等價類中,大小為i的有\(c_i\)個
那就要除去\(\prod(c_i)\),於是式子變成了\(\displaystyle\frac{n!}{\prod b_i\prod c!}\)
就直接利用這些求就完事了
code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=55;
const int M=1005;
int n,m,mod;
int jc[N],ans;
int ksm(int x,int y){
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
x=1ll*x*x%mod;y>>=1;
}
return ret;
}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
int ji[N];
void get_ans(int sum){
int mul=1,top=0,zs=0;
for(re i=1;i<=sum;i++)zs=(zs+ji[i]/2)%mod;
for(re i=1;i<=sum;i++)
for(re j=i+1;j<=sum;j++)
zs=(zs+gcd(ji[i],ji[j]))%mod;
for(re i=1;i<=sum;i++)mul=1ll*mul*ji[i]%mod;
for(re i=2;i<=sum;i++){
top++;if(ji[i]==ji[i-1])continue;
mul=1ll*mul*jc[top]%mod;top=0;
}top++;
mul=1ll*mul*jc[top]%mod;
ans=(ans+1ll*jc[n]*ksm(mul,mod-2)%mod*ksm(m,zs)%mod)%mod;
}
void dfs(int dep,int rest,int mn){
if(!rest){get_ans(dep-1);return ;}
for(re i=mn;i<=rest;i++)
ji[dep]=i,dfs(dep+1,rest-i,i);
}
signed main(){
scanf("%d%d%d",&n,&m,&mod);
jc[0]=1;for(re i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod;
dfs(1,n,1);ans=1ll*ans*ksm(jc[n],mod-2)%mod;
printf("%d",ans);
}
[POJ2154]Color
所以這個和板子題一模一樣,然后我被卡常好久,把long long改成int就A了
主要是這個模數不是質數,所以我們不找逆元了,直接在乘的時候少乘一個就好了
code
#include<cstdio>
using namespace std;
#define re register int
#define ll int
ll t,n,ans,mod;
ll ksm(ll x,ll y){
ll ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return ret;
}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
ll phi(ll x){
ll ret=x;
for(re i=2;i*i<=x;i++){
if(x%i)continue;
ret=ret/i*(i-1);
while(x%i==0)x/=i;
}
if(x>1)ret=ret/x*(x-1);
return ret;
}
signed main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&mod);ans=0;
for(re i=1;i*i<=n;i++){
if(n%i)continue;
ans=(ans+1ll*ksm(n,i-1)*phi(n/i)%mod)%mod;
if(i!=n/i)ans=(ans+1ll*ksm(n,n/i-1)*phi(i)%mod)%mod;
}
printf("%d\n",ans);
}
}
[POJ2888]Magic Bracelet
所以這個就是矩陣乘優化dp轉移???
還是找gcd,然后枚舉循環節大小
我們先寫出轉移方程,dp[i][j]表示前i個位置,第i個位置的顏色是j,不會爆炸的方案數
我們只能一步一步的轉移,只有n個數,我們從0開始,到n結束,所以0和n是同一個點
最后要顏色相同的答案,所以你發現轉移的時候的系數就是會不會爆炸
而每次轉移的系數都是相同的,直接矩陣乘
code
#include<cstdio>
#include<cstring>
using namespace std;
#define re register int
#define ll int
const int M=15;
const ll mod=9973;
ll T,n,m,q,ans;
struct matrix{
ll x[M][M];
matrix(){memset(x,0,sizeof(x));}
matrix operator * (matrix b)const{
matrix c;
for(re i=1;i<=m;i++)
for(re j=1;j<=m;j++)
for(re k=1;k<=m;k++)
c.x[i][j]=(c.x[i][j]+x[i][k]*b.x[k][j])%mod;
return c;
}
}a;
ll kpo(ll x,ll y){
ll ret=1;x%=mod;
while(y){
if(y&1)ret=ret*x%mod;
x=x*x%mod;
y>>=1;
}
return ret;
}
ll ksm(matrix x,ll y){
matrix ret;ll res=0;
for(re i=1;i<=m;i++)ret.x[i][i]=1;
while(y){
if(y&1)ret=ret*x;
x=x*x;y>>=1;
}
for(re i=1;i<=m;i++)
res=(res+ret.x[i][i])%mod;
return res;
}
ll phi(ll x){
ll ret=x;
for(re i=2;i*i<=x;i++){
if(x%i)continue;
ret=ret/i*(i-1);
while(x%i==0)x/=i;
}
if(x>1)ret=ret/x*(x-1);
return ret;
}
signed main(){
scanf("%d",&T);
while(T--){
ans=0;
scanf("%d%d%d",&n,&m,&q);
for(re i=1;i<=m;i++)
for(re j=1;j<=m;j++)
a.x[i][j]=1;
for(re i=1,x,y;i<=q;i++){
scanf("%d%d",&x,&y);
a.x[x][y]=a.x[y][x]=0;
}
for(re i=1;i*i<=n;i++){
if(n%i)continue;
ans=(ans+ksm(a,i)*(phi(n/i)%mod))%mod;
if(i*i!=n)ans=(ans+ksm(a,n/i)*(phi(i)%mod))%mod;
}
printf("%d\n",ans*kpo(n,mod-2)%mod);
}
}