标签:之间 网上 原来 std using ati 结果 单位 vector
https://vjudge.net/problem/UVA-11698
输入n,k,求秩为k的n元置换个数(即k是最小的正整数,使置换$A^k(x)=x$),结果对$2^{31}-1$取模
$1\leqslant N\leqslant 100,1\leqslant K\leqslant 2^{31}-1$
= =很神奇的题目,网上也找不到题解,甚至在官方题解上(有错误)纠结了好久……不会做,最后还是看了别人的提交才过的
首先把置换分解为循环,于是问题变为:
把数字1~N分成一份或几份(份之间可以交换),每一份串成一个循环(可以旋转),所有循环大小的最小公倍数是K,问有多少种分法。
设dp[i][j]为长度为i,最小公倍数是j的分法,那么可以通过枚举最后1所在的循环的大小得到
$dp[i][j]=\sum_{d\mid 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_{d\mid 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)
#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\n",dp[n][(1<<fac.size())-1]);
}
}
标签:之间 网上 原来 std using ati 结果 单位 vector
原文地址:https://www.cnblogs.com/sahdsg/p/12838161.html