【HNOI2008】BZOJ1004 Cards题解(置换群+DP)

题目:BZOJ1004.
题目大意:给定基于 n n n个元素的 m m m个置换组成置换群(未包含单位置换),要求给这 n n n个元素染三种颜色使得第一种颜色数量为 s r sr sr,第二种颜色数量为 s b sb sb,第三种颜色数量为 s g sg sg,求本质不同的染色方案数对 p p p取模.
1 ≤ s r , s b , s g ≤ 20 , 1 ≤ m ≤ 60 , n = s r + s b + s g , m + 1 &lt; p &lt; 100 1\leq sr,sb,sg\leq 20,1\leq m\leq 60,n=sr+sb+sg,m+1&lt;p&lt;100 1sr,sb,sg20,1m60,n=sr+sb+sg,m+1<p<100.

这道题有了颜色数量的限制,不能直接使用Polya定理了.

考虑Polya定理的证明过程,我们也构造以染色方案作为元素构成的集合 S S S,并构造基于 S S S的置换群 G G G.

此时我们发现只需要把染色方案中不满足颜色数量的踢出 S S S就可以进行推导了.

为了套Burnside引理,我们需要计算经过某个置换 A A A后不变的方案数,与Polya定理类似的,这个方案数也需要满足每一个轮换内部染色相同的条件.

此时我们可以把每一个轮换看成一个重量为它的长度的物品,现在把这些物品染色,需要满足染某个颜色的物品重量之和为对应的数量限制,用DP求出方案数即可套用Burnside引理.

时间复杂度 O ( n 4 m ) O(n^4m) O(n4m).

注意初始时没有单位置换,需要加入一个单位置换,并且最后的 1 ∣ G ∣ \frac{1}{|G|} G1 ∣ G ∣ = m + 1 |G|=m+1 G=m+1.

代码如下:

#include<bits/stdc++.h>
using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=60,M=20;

int mod;

int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b){a=add(a,b);}
void ssub(int &a,int b){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}

int Power(int a,int k){
  int res=1;
  for (;k;k>>=1,smul(a,a))
    if (k&1) smul(res,a);
  return res;
}

int n,m,sr,sb,sg;

int p[N+9],vis[N+9];
int a[N+9],ca;

void Get_a(){
  for (int i=1;i<=n;++i) vis[i]=0;
  ca=0;
  for (int i=1;i<=n;++i)
    if (!vis[i]){
      a[++ca]=1;vis[i]=1;
      for (int j=p[i];j^i;j=p[j]) ++a[ca],vis[j]=1;
    }
}

int dp[M+9][M+9][M+9];

void Get_dp(){
  for (int i=0;i<=sr;++i)
    for (int j=0;j<=sb;++j)
      for (int k=0;k<=sg;++k) dp[i][j][k]=0;
  dp[0][0][0]=1;
  for (int i=1;i<=ca;++i)
    for (int j=sr;j>=0;--j)
      for (int k=sb;k>=0;--k)
        for (int t=sg;t>=0;--t){
          if (j>=a[i]) sadd(dp[j][k][t],dp[j-a[i]][k][t]);
          if (k>=a[i]) sadd(dp[j][k][t],dp[j][k-a[i]][t]);
          if (t>=a[i]) sadd(dp[j][k][t],dp[j][k][t-a[i]]);
        }
}

int ans;

Abigail into(){
  scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&mod);
  n=sr+sb+sg;
  for (int i=1;i<=m;++i){
  	for (int j=1;j<=n;++j)
  	  scanf("%d",&p[j]);
  	Get_a();
  	Get_dp();
  	sadd(ans,dp[sr][sb][sg]);
  }
}

Abigail work(){
  for (int i=1;i<=n;++i) p[i]=i;
  Get_a();
  Get_dp();
  sadd(ans,dp[sr][sb][sg]);
}


Abigail outo(){
  printf("%d\n",mul(ans,Power(m+1,mod-2)));
}

int main(){
  into();
  work();
  outo();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值