[HNOI2008]Cards(burnside引理+背包)

题意

有三种颜色分别\(a,b,c\)个,用它们给\(a+b+c\)的序列染色,求染色方案数,同时给定\(m\)个置换,两种方案相同当且仅当存在一个置换使得其中一种方案变成另一种。数据保证任意一种置换排列方式都可以只用一个置换替代,且对于任一个置换,都存在另一个置换是它的逆元

Sol

\(Burnside\)引理:对于一个置换群,用\(c(a_i)\)表示在置换\(a_i\)下不变的元素个数,那么本质不同的方案数为:

\(L=\frac{\Sigma_{i=1}^{|G|}c(a_i)}{|G|}\)

其中\(|G|\)表示群的大小

根据原题的限制,可以看出这些置换再加上一个单位元即可满足群的定义,构成一个置换群

对于每一个置换\(a_i\),我们想要知道有多少种染色方案使得这种方案在\(a_i\)下不变

定义:\(n阶循环\)\((a_1,a_2,...,a_n)=\begin{pmatrix} a_1 & a_2... & a_n \\ a_2 & a_3... & a_1 \end{pmatrix}\)

(循环可以看做一个首尾顺次连接的环,反正我是这么看的

容易发现如果对一个循环进行染色且使得置换前后颜色不变,必须将它们染成同一种颜色

任一个置换可以写成几个互不相交的循环的乘积,其中互不相交指的是两个循环间没有公共位置\(a_i\)

举个栗子:

\(\begin{pmatrix} 1 & 2 & 3 & 4 & 5\\ 3 & 5 & 1 & 4 & 2 \end{pmatrix}=(13)(25)(4)\)

那么将置换\(a_i\)分成\(k\)个循环,满足条件的方案\(j\)应该满足它的每一个循环位置上的颜色是一样的。用类似求环的方法求出所有循环,用\(dp[h][f][u]\)表示当前三种颜色各已经用了\(h,f,u\)个的满足条件的方案数,以一整个循环为一个单位当作背包问题进行处理,\(c(a_i)=f[a][b][c]\),最后带入公式即可

注意:前面加入的单位元也要算作一个置换,所以总共应该有\(m+1\)个置换

Code:

#include<bits/stdc++.h>
#define N 70
using namespace std;
int a[4],n,m,mod,ans;
int f[22][22][22];
int t[N],size[N],tot;
bool vis[N];

template <class T>
void read(T &x)
{
    char c;int sign=1;
    while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
    while((c=getchar())>='0'&&c<='9') x=x*10+c-48; x*=sign;
}
int quickpow(int a,int b)
{
    int ret=1;
    while(b)
    {
        if(b&1) ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}
int calc()
{
    tot=0;
    memset(vis,0,sizeof(vis));
    memset(f,0,sizeof(f));
    f[0][0][0]=1;
    //找循环
    for(int i=1;i<=n;++i)
    {
        if(!vis[i])
        {
            size[++tot]=1;
            vis[i]=1;
            int now=t[i];
            while(now!=i) vis[now]=1,size[tot]++,now=t[now];
        }
    }
    for(int i=1;i<=tot;++i)
    {
        for(int b=a[1];b>=0;--b)
        {
            for(int c=a[2];c>=0;--c)
            {
                for(int d=a[3];d>=0;--d)
                {
                    if(b>=size[i]) f[b][c][d]=(f[b][c][d]+f[b-size[i]][c][d])%mod;
                    if(c>=size[i]) f[b][c][d]=(f[b][c][d]+f[b][c-size[i]][d])%mod;
                    if(d>=size[i]) f[b][c][d]=(f[b][c][d]+f[b][c][d-size[i]])%mod;
                }
            }
        }
    }
    return f[a[1]][a[2]][a[3]]%mod;
}
int main()
{
    read(a[1]);read(a[2]);read(a[3]);
    n=a[1]+a[2]+a[3];
    read(m);read(mod);
    for(int i=1;i<=m;++i)
    {
        for(int j=1;j<=n;++j) read(t[j]);
        ans=(ans+calc())%mod;
    }
    for(int i=1;i<=n;++i) t[i]=i;//单位置换 
    ans=(ans+calc())%mod;
    cout<<ans*quickpow(m+1,mod-2)%mod<<endl;
    return 0;
}

转载于:https://www.cnblogs.com/Chtholly/p/11420774.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值