1. 程式人生 > >組合數學之burnside polya 入門,進階

組合數學之burnside polya 入門,進階

假如一個置換有k個迴圈,總共有m種顏色可選,那麼這個置換對應的不動點數量就為m^k個

一個置換群對應的不動點數量就為  該群中所有置換對應的m^累加再除以該群中置換的數量。

即:

L=1|G|(m^c(g1)+m^c(g2)+...+m^c(gs))

其中,G={g1,g2,...,gs},|G|是置換操作的個數s,c(gi)是置換gi的迴圈節數

其中兩個比較經典的模型就是  正方形塗色模型   環排列問題

當題目問到有完全不同的方案數之類,一般就是對應了該置換群的不動點數量。

正方形塗色模型可以看作是環排序問題的累加,相當於一層層環 構成了一個正方形。

假如環存在n個可以塗色的地方,有m種顏色可以選擇,問共有多少種 兩兩間不可以通過 旋轉 翻折得到 的方案。

而對於環排序問題,置換群存在翻轉和旋轉兩種型別的置換:

1.旋轉:共有n種置換,旋轉1格,2格子……n格 對應的迴圈長度為n,n/gcd(2,n)......迴圈節數分別為 gcd(n,1),gcd(n,2)......(迴圈節數是指經過多少次相同的置換能回到原來的狀態)   所以對應的不動點數分別為 pow(m,gcd(n,1)),pow(m,gcd(n,2))......

2.翻折:分奇偶討論

奇數:對稱軸為某個頂點和中心的連線,共有n種置換,每個置換的迴圈節數為(n-1)/2 +1->對稱軸上的點迴圈節數為1

偶數:第一種同上,由於該對稱軸穿過兩個頂點,所以共有n/2種置換,每種置換的迴圈節數為(n-2)/2+2 ->n/2+1

            第二種 :以相鄰頂點的中點與頂點連線,共有n/2種置換 每種置換對應 n/2個迴圈節

分別對旋轉和翻折求和之後除以2|G|

#include <iostream>
#include <cmath>
using namespace std;
#define ll unsigned long long
int gcd(int n,int m)
{
    return m==0?n:gcd(m,n%m);
}
ll fac[30];
void init()
{
    fac[0]=1;
    for(int i=1;i<=28;i++)
    {
        fac[i]=fac[i-1]*i;
    }
}

ll solve(int n)
{
    ll ans=0;

    for(int i=1;i<=n;i++)
    {
        ans+=pow(1ll*3,gcd(n,i));
    }
    //ans+=n;
    if(n%2==1)
    {

        ans+=n*pow(1ll*3,(n+1)/2);
    }
    else
    {
        ans+=(n/2)*pow(1ll*3,(n/2+1));
        ans+=(n/2)*pow(1ll*3,(n/2));
    }
    ans/=(2*n);


    return ans;
}



int main()
{
    init();
    int n;
    while(cin>>n)
    {
        if(n==-1)
        {
            return 0;
        }
        else if (n == 0)
        {
            cout << 0 << endl;
        }
        else
        {
            cout << solve(n) << endl;
        }
    }


    return 0;
}

正方形塗色問題:看作累加的環塗色問題,分奇偶討論  因為奇數每次都有一箇中心都有一個鬼畜的不動點

打擾了打擾了 設個mod不好麼。。 大資料,java做    

#include <iostream>
using namespace std;
#define ll unsigned long long
ll gcd(ll a,ll b)
{
    ll t;
    while(b)
    {
        t = b;
        b = a % b;
        a = t;
    }
    return a;
}
ll quick_pow_mod(ll a,ll b)//二進位制思想 二分乘  a^b=(a^2)^(b-1) 遞推
{

    ll res=1;//乘法初始化為1
    while(b)
    {
        if(b & 1)//判斷b的二進位制最後一位是否為1(&運算同為1則取1)
        {
            res=(res*a);//每次乘都取一次模,防止資料溢位 如果二進位制的b這一位上為1,更新res
        }//
        a=(a*a);//b每移一位,更新一次a,相當於乘2
        b=b>>1;
    }
    return res;
}
//正方形塗色問題可以劃為環旋轉問題,正方形看作是環的疊加
void solve(int n,int m)//旋轉可以順時針0->1 90->4 180->2 270->4,翻折分奇偶討論
{
    if(n==0)cout<<0<<endl;
    else if(n==1)
    {
        cout<<m+1<<endl;
    }
    else
    {//不動點的個數看作是環的累加
        ll ans = 0;

        if(n%2)//奇數的翻折中間的軸不變
        {
            ans+=2ll*quick_pow_mod(m,(n*n-1)/4+1);//旋轉90/270
            //cout<<ans<<endl;
            ans+=1ll*quick_pow_mod(m,(((n*n-1)/2)+1));//180
            //cout<<ans<<endl;
            ans+=quick_pow_mod(m,n*n);//0
            //cout<<ans<<endl;
            ans+=2*quick_pow_mod(m,((n*n-n)/2)+n); //翻折 斜著
            //cout<<ans<<endl;
            ans+=2*quick_pow_mod(m,((n*n-n)/2)+n); //橫著
            //cout<<ans<<endl;
        }
        else
        {
            ans+=2ll*quick_pow_mod(m,(n*n/4));//旋轉90/270
            //cout<<ans<<endl;
            ans+=1ll*quick_pow_mod(m,(n*n/2));//180
            //cout<<ans<<endl;
            ans+=quick_pow_mod(m,n*n);//0
            //cout<<ans<<endl;
            ans+=2*quick_pow_mod(m,((n*n-n)/2)+n);//翻折 斜著
            //cout<<ans<<endl;
            ans+=2*quick_pow_mod(m,n*n/2);//橫著
            //cout<<ans<<endl;
        }
        ans/=8;
        cout<<ans<<endl;
    }
}
int main()
{
    // n*n的棋盤上有c種顏色可以選塗色,求本質不同(旋轉反射)的方案數
    int n,c;
    while(cin>>n>>c)
    {
        solve(n,c);
    }
    return 0;
}

burnside polya進階:

UVA - 11255 Necklace

題目大意:共有三種顏色,這三種顏色的可用次數均限定,問有多少種本質不同的項鍊填色方案

先考慮摺疊的情況,共有n種置換

對第i個珠子順時針旋轉k個珠子後它的軌跡就是:i , i+k , ...... , (i+k*t)%n直到這個珠子回到i為止

求得這個迴圈的長度就為n/gcd(n,k),而對於同一種置換,這個置換中的所有迴圈的迴圈長度是相同的,

則這個迴圈的迴圈節就為:gcd(n,k) ,即有gcd(n,k) 個互不相交的軌跡

要構成不動點必須滿足這個點旋轉到這個點的所有軌跡上都滿足顏色不變,也就是說所有顏色都要滿足

color_num[i]%len==0,這樣塗色方案才能在每個軌跡中均分。

假設總共有sum種可以用的顏色總數,color_num[i]表示第i種顏色可以完整塗完一個軌跡的次數

這樣對於一個置換下的塗色方案數就為:C[sum][color_num[1]]*C[sum-color_num[1]][color_num[2]]......

後面是一個組合數學問題

再考慮旋轉,分奇偶討論,如果對稱軸穿過珠子的話,迴圈的時候先把這種珠子的數量-- 最後再加回來


#include <iostream>
#include <cstring>
using namespace std;
#define ll long long
ll c[50][50];
int a[5];
int b[5];//用於複製a陣列
int n;

int gcd(int x, int y)
{
    return y == 0 ? x : gcd(y, x % y);
}
void init()
{
    memset(c,0,sizeof(c));
    c[0][0]=1;
    for(int i=1;i<=48;i++)
    {
        c[i][0]=c[i][i]=1;
        for(int j=1;j<i;j++)
        {
            c[i][j]=c[i-1][j]+c[i-1][j-1];
        }
    }
}

ll work(int len)
{
    ll res=1;
    ll sum=0;//記錄所有顏色中可以塗色的輪數之和
    for(int i=1;i<=3;i++)
    {
        if(b[i]%len!=0)return 0;
        b[i]/=len;
        sum+=b[i];
    }
    for(int i=1;i<=3;i++)
    {
        res*=c[sum][b[i]];
        sum-=b[i];
    }
    return res;
}


ll xz()
{
    ll res=0;
    for(int i=1;i<=n;i++)//旋轉可以從0到n-個位置
    {
        int len=n/gcd(n,i);
        memcpy(b,a, sizeof(a));
        res+=work(len);
    }
    return res;
}

ll fz()
{
    ll res=0;
    if(n%2==1)
    {
        for(int i=1;i<=3;i++)
        {
            memcpy(b,a, sizeof(a));
            if(b[i]<1)continue;
            b[i]--;//奇數個元素的情況下選一個元素和中心點為轉軸
            res += n * work(2);
        }
    }
    else
    {
        for(int i=1;i<=3;i++)
        {
            for(int j=1;j<=3;j++)
            {
                memcpy(b,a,sizeof(a));
                if(b[i]<1||b[j]<1)continue;
                b[j]--;b[i]--;
                res+=1ll*(n/2)*(work(2));//對稱軸過兩個珠,可以選n/2個對稱軸
            }
        }
        memcpy(b,a,sizeof(a));
        res+=1ll*(n/2)*work(2);//偶數個元素時,選兩個相鄰元素中點和中心連線翻著,對稱軸不穿過珠子
    }
    return res;
}


int main()
{
    init();
    int t;
    cin>>t;
    while(t--)
    {
        memset(a,0,sizeof(a));
        cin>>a[1]>>a[2]>>a[3];
        n=a[1]+a[2]+a[3];
        ll ans=0;
        ans=(xz()+fz())/(2*n);
        cout<<ans<<endl;
    }
    return 0;