您的位置:首页 > 其它

[置换群+背包] BZOJ1004: [HNOI2008]Cards

2017-04-09 19:43 417 查看

题意

小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红,蓝,绿。

小春发明了M种不同的洗牌法,问Sun有多少种不同的染色方案。

两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗成另一种。

输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代

替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。

答案可能很大,只要求出答案除以P的余数(P为质数).

题解

完全裸的一个置换群模型。只需要注意到各种颜色数是有规定的,显然不能直接3nc(g) 直接得到对某置换g保持不变的的方案数。

那如何做呢?对于某个置换g,若某一着色方案对g保持不变,那么它对应g中的各个循环节中的元素都必须染成同样的颜色。

这样就可以转化成背包求方案数了。设f[i][j][k] 表示染了i个红色,j个蓝色,k个绿色的方案数。转移就是把某个循环节的元素全部涂成某一种颜色。最后有除法,求一下逆元就好了。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=75;
int allv[3],n,m,MOD,a[maxn][maxn],ans,f[maxn][maxn][maxn],w[maxn];
bool vis[maxn];
int get(int id){
memset(w,0,sizeof w);
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++) if(!vis[i]){
w[0]++; int now=i;
do w[w[0]]++, vis[now]=true, now=a[id][now]; while(!vis[now]);
}
memset(f,0,sizeof f); f[0][0][0]=1;
for(int i=1;i<=n;i++)
for(int j0=allv[0];j0>=0;j0--)
for(int j1=allv[1];j1>=0;j1--)
for(int j2=allv[2];j2>=0;j2--){
int &fnow=f[j0][j1][j2];
if(j0>=w[i]) fnow=(fnow+f[j0-w[i]][j1][j2])%MOD;
if(j1>=w[i]) fnow=(fnow+f[j0][j1-w[i]][j2])%MOD;
if(j2>=w[i]) fnow=(fnow+f[j0][j1][j2-w[i]])%MOD;
}
return f[allv[0]][allv[1]][allv[2]];
}
int power(int a,int b){
if(!b) return 1;
if(b&1) return (a*power(a,b-1))%MOD;
int t=power(a,b/2); return (t*t)%MOD;
}
int main(){
freopen("bzoj1004.in","r",stdin);
freopen("bzoj1004.out","w",stdout);
scanf("%d%d%d%d%d",&allv[0],&allv[1],&allv[2],&m,&MOD); n=allv[0]+allv[1]+allv[2];
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
m++; for(int j=1;j<=n;j++) a[m][j]=j;
for(int i=1;i<=m;i++) ans=(ans+get(i))%MOD;
ans=(ans*power(m,MOD-2))%MOD;
printf("%d\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: