bzoj 1004 Cards

1004: [HNOI2008]Cards
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 3605 Solved: 2155
[Submit][Status][Discuss]
Description

  小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有
多少种染色方案,Sun很快就给出了答案.进一步,小春要求染出Sr张红色,Sb张蓝色,Sg张绝色.他又询问有多少种方
案,Sun想了一下,又给出了正确答案. 最后小春发明了M种不同的洗牌法,这里他又问Sun有多少种不同的染色方案.
两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗
成另一种.Sun发现这个问题有点难度,决定交给你,答案可能很大,只要求出答案除以P的余数(P为质数).

Input

  第一行输入 5 个整数:Sr,Sb,Sg,m,p(m<=60,m+1 < p < 100)。n=Sr+Sb+Sg。
接下来 m 行,每行描述一种洗牌法,每行有 n 个用空格隔开的整数 X1X2…Xn,恰为 1 到 n 的一个排列,
表示使用这种洗牌法,第 i位变为原来的 Xi位的牌。输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代
替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。

Output

  不同染法除以P的余数

Sample Input

1 1 1 2 7

2 3 1

3 1 2

Sample Output

2

HINT

  有2 种本质上不同的染色法RGB 和RBG,使用洗牌法231 一次可得GBR 和BGR,使用洗牌法312 一次 可得BRG

和GRB。

100%数据满足 Max{Sr,Sb,Sg}<=20。

Source


【分析】

辣鸡置换学不会
burnside引理
分别计算每一个置换下的循环节个数,那么循环节各部分相互独立,循环节内部只能染一个颜色= =
啪啪啪一顿背包

现在才学burnside…NOI药丸啊


【代码】

//bzoj 1004 Cards
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int mxn=65;
bool vis[mxn];
int n,m,p,a,b,c,ans;
int A[mxn],f[mxn],sum[mxn],dp[mxn][25][25];
inline int ksm(int x,int k)
{
    if(k==0) return 1;
    if(k==1) return x%p;
    int tmp=ksm(x,k>>1);
    if(k&1) return tmp*tmp*x%p;
    else return tmp*tmp%p;
}
inline int dfs(int now,int cnt)
{
    if(vis[now]) return cnt;
    vis[now]=1,dfs(A[now],cnt+1);
}
inline int DP()
{
    int i,j,k,tot=0;
    memset(vis,0,sizeof vis);
    memset(dp,0,sizeof dp);
    fo(i,1,n)
      if(!vis[A[i]])
        f[++tot]=dfs(A[i],0);
    fo(i,1,tot) sum[i]=sum[i-1]+f[i];
    dp[0][0][0]=1;
    fo(i,1,tot)
      fo(j,0,a)
        fo(k,0,b)
        {
            if(j>=f[i]) dp[i][j][k]+=dp[i-1][j-f[i]][k];
            if(k>=f[i]) dp[i][j][k]+=dp[i-1][j][k-f[i]];
            if(sum[i]-j-k>=f[i]) dp[i][j][k]+=dp[i-1][j][k];
            dp[i][j][k]%=p;
        }
    return dp[tot][a][b];
}
int main()
{
    int i,j,k;
    scanf("%d%d%d%d%d",&a,&b,&c,&m,&p);
    n=a+b+c;
    fo(i,1,m)
    {
        fo(j,1,n) scanf("%d",&A[j]);
        ans=(ans+DP())%p;
    }
    fo(i,1,n) A[i]=i;
    ans=(ans+DP())%p;
    printf("%d\n",ans*ksm(m+1,p-2)%p);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值