【bzoj1004】Cards【Polya计数定理】【递推】

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1004
这是一道Polya好题~
根据那个什么引理,本质不同的方案数等于每个置换下不同的方案数的平均值。
但是Polya定理是:

l=1|G|fGkm(f)

而这里是有三种颜色,求每个置换里每个循环涂同样的颜色的方案数。
那么我们可以递推。设 f(i,j,k) 表示在当前置换中的第1..i个循环中,涂红色的有j张牌,涂蓝色的有k张牌,并且设 w(i) 表示第i个循环的长度。
那么枚举第i个循环被涂成什么颜色,有
f(i,j,k)=f(i1,j,k)+f(i1,jw(i),k)+f(i1,j,kw(i))

注意 jw(i) kw(i) 只有不小于0才能转移。
推完了取 f(n,sr,sb) 累加到答案里并取模即可。最后乘上置换数目对p的逆元就行了。
还有一个地方:因为我不知道数据有多猥琐,所以判断了一下,如果他没给1,2,3,…,n这样的置换的话,我才把它加上。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
void exgcd(ll a,ll b,ll &d,ll &x,ll &y){
    if(!b){d=a;x=1;y=0;}
    else{exgcd(b,a%b,d,y,x);y-=x*(a/b);}
}
inline ll inv(ll a,ll p){
    ll d,x,y;
    exgcd(a,p,d,x,y);
    return d==1?(x+p)%p:-1;
}
int per[61][100];
int length[61][100];
bool visit[100];
ll f[100][100][100];
int n;
ll ans;
void cut(int cur,int *a){
    memset(visit,0,sizeof visit);
    for(int i=1;i<=n;++i)if(!visit[i]){
        int j=i,tot=0;
        do{
            visit[j]=true;
            tot++;
            j=a[j];
        }while(j!=i);
        length[cur][++length[cur][0]]=tot;
    }
//  printf("分成了%d个循环。\n",length[cur][0]);
}
int main(){
    ll sr,sb,sg,m,p;
    scanf("%lld%lld%lld%lld%lld",&sr,&sb,&sg,&m,&p);
    n=sr+sb+sg;
    bool hehe=false;
    for(int i=1;i<=m;++i){
        for(int j=1;j<=n;++j){
            scanf("%d",per[i]+j);
        }
        cut(i,per[i]);
        if(length[i][0]==n)hehe=1;
    }
    if(!hehe){
        ++m;
        for(int i=1;i<=n;++i)per[m][i]=i;
        cut(m,per[m]);
    }
        for(int i=1;i<=m;++i){
        int *w=length[i];
        memset(f,0,sizeof f);
        f[1][w[1]][0]=f[1][0][w[1]]=f[1][0][0]=1;
        for(int t=2;t<=w[0];++t)
            for(int j=0;j<=sr;++j)
                for(int k=0;k<=sb;++k){
                    f[t][j][k]=f[t-1][j][k];
                    if(j>=w[t])f[t][j][k]=(f[t][j][k]+f[t-1][j-w[t]][k])%p;
                    if(k>=w[t])f[t][j][k]=(f[t][j][k]+f[t-1][j][k-w[t]])%p;
                }
        ans=(ans+f[w[0]][sr][sb])%p;
    }
    ans=ans*inv(m,p)%p;
    cout<<ans<<"\n";
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值