https://vjudge.net/problem/UVA-11698
题目
输入n,k,求秩为k的n元置换个数(即k是最小的正整数,使置换$A^k(x)=x$),结果对$2^{31}-1$取模
$1leqslant Nleqslant 100,1leqslant Kleqslant 2^{31}-1$
题解
= =很神奇的题目,网上也找不到题解,甚至在官方题解上(有错误)纠结了好久……不会做,最后还是看了别人的提交才过的
首先把置换分解为循环,于是问题变为:
把数字1~N分成一份或几份(份之间可以交换),每一份串成一个循环(可以旋转),所有循环大小的最小公倍数是K,问有多少种分法。
设dp[i][j]为长度为i,最小公倍数是j的分法,那么可以通过枚举最后1所在的循环的大小得到
$dp[i][j]=sum_{dmid K} A_{i-1}^{j-1}dp[i-d][?]$
边界为dp[0][1]=1,因为LCM(1,x)=x,可以看成单位元= =
可以发现?处可以取很多数……只要LCM(?,d)=j都可以取
其中$A_{i-1}^{j-1}$表示这个环的元素,因为1就在里面所以不管,循环按照顺时针拆成链,剩下的元素按照大小顺序对应到原来的1,2,3,…,i-d
那么就是枚举?和d,然后LCM计算出j
时间复杂度O(n^3)(枚举?、d、i,通过?和d计算j)
然后可以状态压缩一下,因为根据算术基本定理,最小公倍数是指数取最大值
那么只要指数没取到最大值就可以分成一类情况,只能一步登天,没有中间状态
设$dp[i][k]$为长度为$i$,k表示每一个指数是否取到最大值
那么可以写出
$dp[i][k|k_d]=sum_{dmid K} A_{i-1}^{j-1}dp[i-d][k]$
k的第i位表示第i个指数是否达到了题目要求的最大指数
根据乘法$2*3*5*7*11*13*17*19*23*29>2^{31}-1$,可以得到k最多有10位
边界为dp[0][0]=1
时间复杂度O(2^log(k) n^2)(枚举k、d和i)
AC代码
#include<cstdio> #include<cmath> #include<cstring> #include<vector> #define MO ((1ull<<31)-1) using namespace std; int A[107][107]; inline void db() { A[0][0]=1; for(int i=1; i<=100; i++) { A[i][0]=1; for(int j=1; j<=i; j++) { A[i][j] = 1ll*A[i-1][j-1]*i%MO; } } } unsigned dp[107][1027]; vector<int> fac, dd, dk; int main() { db(); memset(dp,0,sizeof dp); int T; scanf("%d", &T); while(0<T--) { int n,k; scanf("%d%d", &n, &k); int kk=k; fac.clear(); dd.clear(); dk.clear(); for(int i=2; i<=n; i++) if(kk%i==0) { int now=1; do {kk/=i; now*=i;} while(kk%i==0); fac.push_back(now); } if(kk>1) {puts("0"); continue;} for(int i=1; i<=n; i++) if(k%i==0) { int k=0; for(int j=0; j<(int)fac.size(); j++) if(i%fac[j]==0) { k|=1<<j; } dk.push_back(k); dd.push_back(i); } memset(dp,0,sizeof dp); dp[0][0]=1; for(int i=1; i<=n; i++) { for(int j=0; j<(int)dd.size(); j++) { for(int k=0; k<(1<<fac.size()); k++) if(i>=dd[j]) { dp[i][k|dk[j]] = (dp[i][k|dk[j]]+1ll*dp[i-dd[j]][k]*A[i-1][dd[j]-1])%MO; } } } printf("%d ",dp[n][(1<<fac.size())-1]); } }