BZOJ1004 [HNOI2008]Cards(并查集+动态规划)

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的余数

 

题解:

如果某一个状态可以通过洗牌到达另一种状态,就从这个状态向另一种状态连一条有向边。

最终的图一定全部是双向边,并且所有的状态组成了一些团,团的个数即为答案。

问题是如何求出团的个数。

遍历每种洗牌方式,将每一张牌的编号和它洗牌后的编号放在一个集合中,这个过程维护一个并查集,并保存每个集合的大小。

然后开一个三维dp数组,对于每个集合,dpi[k]表示当前颜色的染色数目,然后我自闭了,真的难..........

 

#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
int dp[maxn][maxn][maxn];
int x,y;
int father[maxn];
int num[maxn];
int g[maxn][maxn];
int a,b,c,m,p;
int n;
int findfather (int x) {
    int a=x;
    while (x!=father[x]) x=father[x];
    while (a!=father[a]) {
        int z=a;
        a=father[a];
        father[z]=x;
    }
    return x;
}
void Union (int a,int b) {
    int faA=findfather(a);
    int faB=findfather(b);
    if (faA==faB) return;
    father[faA]=faB;
    num[faB]+=num[faA];
}

void exgcd (int a,int b) {
    if (!b) x=1,y=0;
    else {
        exgcd(b,a%b);
        int u=y;
        int v=x-a/b*y;
        x=u;
        y=v;
    }
}


int main () {
    scanf("%d%d%d%d%d",&a,&b,&c,&m,&p);
    n=a+b+c;
    for (int i=1;i<=m;i++) {
        for (int j=1;j<=n;j++)
            scanf("%d",&g[i][j]);
    }
    m++;
    for (int i=1;i<=n;i++)
        g[m][i]=i;
    int ans=0;
    for (int i=1;i<=m;i++) {
        for (int j=1;j<=n;j++) 
            father[j]=j,num[j]=1;
        for (int j=1;j<=n;j++)
            Union (j,g[i][j]);
        memset(dp,0,sizeof(dp));
        dp[0][0][0]=1;
        for (int j=1;j<=n;j++) {
            if (father[j]==j) {
                int k=num[j];
                for (int ii=a;ii>=0;ii--) 
                    for (int jj=b;jj>=0;jj--) {
                        for (int kk=c;kk>=0;kk--) {
                            
                            if (ii>=k) dp[ii][jj][kk]=(dp[ii][jj][kk]+dp[ii-k][jj][kk])%p;
                            if (jj>=k) dp[ii][jj][kk]=(dp[ii][jj][kk]+dp[ii][jj-k][kk])%p;
                            if (kk>=k) dp[ii][jj][kk]=(dp[ii][jj][kk]+dp[ii][jj][kk-k])%p;
                        }
                    }
            }
        }
        ans=(ans+dp[a][b][c])%p;
    }
    exgcd(p,m);
    while (y<0) y+=p;
    ans=(ans*y)%p;
    printf("%d
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/zhanglichen/p/12491086.html