组合数学之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|

hdu-1817 https://vjudge.net/problem/HDU-1817典型的环涂色问题,用polya记数原理解决

#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;
}

正方形涂色问题:看作累加的环涂色问题,分奇偶讨论  因为奇数每次都有一个中心都有一个鬼畜的不动点

 hdu-1812   https://vjudge.net/problem/HDU-1812

打扰了打扰了 设个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

https://vjudge.net/problem/UVA-11255

题目大意:共有三种颜色,这三种颜色的可用次数均限定,问有多少种本质不同的项链填色方案

先考虑折叠的情况,共有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;

猜你喜欢

转载自blog.csdn.net/neuq_zsmj/article/details/81740610
今日推荐