看到这道题,如果之前做过相关的一些题的话应该会很快知道这是一道重复计数类问题。解决这类问题的话不得不提到的就是:Burnside定理和Pólya计数法,由于我数学比较弱,所以这里也不介绍了。具体可见陈瑜希的论文《Pólya计数法的应用》
解决了上面的问题后,接下来的问题就是怎样求经过置换后方案不变的总数。
我们可以先将置换分解为循环,显然每个循环中的卡片颜色必须完全相同。
将每个循环看作一个物品,每种卡片的总数看作一个背包,就可以转化为一个求背包方案的问题。
方程是:f[i][j][k] = f[i-g[t]][j][k] + f[i][j-g[t]][k] + f[i][j][k-g[t]]
这样就求出了每种置换卡片经过置换后方案不变的总数,记为tmp,则ans = (tmp / m)mod p
这就需要用到乘法逆元,求乘法逆元可以使用扩展欧几里徳算法(需要注意求出的x可能为负数),这样,整个算法就完成了。
代码:
#include<cstdio> #include<cstring> using namespace std; const int maxn = 100; const int maxm = 50; int sr,sb,sg,n,m,p; int x,y,d; int s[maxn],g[maxn][maxn],f[maxm][maxm][maxm]; bool flag[maxn]; void init() { freopen("bzoj1004.in","r",stdin); freopen("bzoj1004.out","w",stdout); } void readdata() { scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&p); m += 1;n = sr + sb + sg; for(int i = 1;i < m;i++) { for(int j = 1;j <= n;j++) { scanf("%d",&g[i][j]); } } for(int i = 1;i <= n;i++)g[m][i] = i; } int DP(int k) { memset(flag,false,sizeof(flag)); memset(f,0,sizeof(f)); int size = 0,next; for(int i = 1;i <= n;i++) { if(!flag[i]) { s[++size] = 1; flag[i] = true; next = i; while(!flag[g[k][next]]) { flag[g[k][next]] = true; s[size]++; next = g[k][next]; } } } f[0][0][0] = 1; for(int t = 1;t <= size;t++) { for(int i = sr;i >= 0;i--) for(int j = sb;j >= 0;j--) for(int k = sg;k >= 0;k--) { if(i >= s[t])f[i][j][k] = (f[i-s[t]][j][k] + f[i][j][k]) % p; if(j >= s[t])f[i][j][k] = (f[i][j-s[t]][k] + f[i][j][k]) % p; if(k >= s[t])f[i][j][k] = (f[i][j][k-s[t]] + f[i][j][k]) % p; } } return f[sr][sb][sg]; } void extend_gcd(int a,int b,int &d,int &x,int &y) { if(!b) { d = a; x = 1; y = 0; } else { extend_gcd(b,a % b,d,y,x); y -= x * (a / b); } } void solve() { int ans = 0; for(int i = 1;i <= m;i++) { ans = (ans + DP(i)) % p; } extend_gcd(m,p,d,x,y); ans = (ans * ((x + p) % p)) % p; printf("%d",ans); } int main() { init(); readdata(); solve(); return 0; }