BZOJ 1004 Cards

题面

题意

  给出 \(a,b,c\) 代表三种颜色的卡片数量,现在给出 \(m\) 个置换,求出在这些置换下本质不同的卡片排列的数量。保证给出的置换满足对任意一个置换都存在一个置换将其变回原状态,同时任意的置换的组合都能用一个置换代替。

题解

  基本上是 Burnside 引理的裸题。将 \((1,2,\dots,n)\) 加入 \(m\) 个置换里,根据 Burnside 引理,这样的 \(m+1\) 个置换下的等价类(也就是本质不同的卡片排列数量)等于 \(m+1\) 个置换中不动点(也就是在某一个置换下不产生变化的排列)的数量的平均值。

  因此,问题转化成了如何求出每一个置换中不动点的数量。把置换的第 \(i\)\(x_i\) 看作是从 \(i\) 连向 \(x_i\) 的一条单向边,就可以得到一个 \(n\) 个结点,由数个有向环构成的图。我们发现,如果排列在一个置换下不产生变化,那么环上所有卡片颜色都相同。我们可以把这个问题转换成给环上色,使其满足三种颜色的结点数量各为 \(a,b,c\) 的方案数。这个问题可以用三维背包来解决。


代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=25,maxm=65;
int mod;
int dp[maxn][maxn][maxn];
int pow(int a,int b){
    int ans=1;
    while (b){
        if (b&1) ans=ans*a%mod;
        a=a*a%mod; b>>=1;
    }
    return ans;
}
int inv(int n){ return pow(n,mod-2); }
int x[maxm];
bool vis[maxm];
vector<int> vec;
int a,b,c,n;
void get(){
    int i,u,cnt;
    vec.clear();
    for (i=1;i<=n;i++) vis[i]=false;
    for (i=1;i<=n;i++) if (!vis[i]){
        u=x[i]; cnt=1; vis[u]=true;
        while (u!=i){
            u=x[u]; cnt++; vis[u]=true;
        }
        vec.push_back(cnt);
    }
}
int solve(){
    int i,j,k,v,p;
    memset(dp,0,sizeof(dp));
    dp[0][0][0]=1;
    for (p=0;p<vec.size();p++){
        v=vec[p];
        for (i=a;i>=0;i--) for (j=b;j>=0;j--) for (k=c;k>=0;k--){
            if (i>=v) dp[i][j][k]+=dp[i-v][j][k];
            if (j>=v) dp[i][j][k]+=dp[i][j-v][k];
            if (k>=v) dp[i][j][k]+=dp[i][j][k-v];
            dp[i][j][k]%=mod;
        }
    }
    return dp[a][b][c];
}
int main(){
    int i,j,m;
    int ans;
    scanf("%d%d%d%d%d",&a,&b,&c,&m,&mod);
    n=a+b+c;
    vec.resize(n);
    for (i=0;i<n;i++) vec[i]=1;
    ans=solve();
    for (i=0;i<m;i++){
        for (j=1;j<=n;j++) scanf("%d",&x[j]);
        get();
        ans+=solve();
    }
    ans=ans%mod*inv(m+1)%mod;
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Kilo-5723/p/12004106.html