题目链接:HDU - 4623
题意:将1~n,n个数重新排列组合,使得每相邻的两个数互质;问总共有多少中方案;
思路:n最大是28,首先想到状压DP,2^28=268435456,肯定会爆栈;所以还需要优化一下;通过观察可以发现,质因子相同的数可以看做一类;也就是说,6, 12, 24可以放到一组中,用一位表示,那么,将28个数分组后就构成了每个位不同进制的数字,由计算的共state=1727999种状态;时间复杂度:O(state*28*28), 大约1354751216;
#include <bits/stdc++.h> using namespace std; const int maxn=2e6; //Ꮧ对1~28分组,有相同质因子的为一组,又1, 17, 19, 23与任何数互质,所以将其分为一组,在同一组有相同的对外性; int group[]={0, 0, 1, 2, 1, 3, 4, 5, 1, 2, 6, 7, 4, 8, 9, 10, 1, 0, 4, 0, 6, 11, 12, 0, 4, 3, 13, 2, 9}; //提取每一组的代表数; int digit[]={1, 2, 3, 5, 6, 7, 10, 11, 13, 14, 15, 21, 22, 26}; //prime[i][j]表示i组的数与j组的是否互质; bool prime[20][20]; int gcd(int a, int b){ return b==0?a:gcd(b, a%b); } //初始化prime数组; void init(){ //这里为什么i,j都是0~13?可以保留疑问,继续向下看; for(int i=0; i<14; i++){ for(int j=0; j<14; j++){ prime[i][j]=(gcd(digit[i], digit[j])==1?1:0); } } } //bit[i]表示i位是bit[i]进制, num表示给出的范围总共包括的组数,也就表示一共有多少位; int bit[20], dp[maxn][20], num, mod; //计算cnt数组的状态下,表示的变进制数;cnt[i]表示第i为是cnt[i]; int get_state(int *cnt){ int state=0; for(int i=0; i<=num; i++){ state=state*bit[i]+cnt[i]; } return state; } //计算在state状态下的cnt数组; void get_cnt(int state, int *cnt){ for(int i=num; i>=0; i--){ cnt[i]=state%bit[i]; state/=bit[i]; } } //suf[i]表示在第i位加1,十进制数增加suf[i]。例如二进制100=4,1000=8; int suf[20]; void get_suf(){ suf[num]=1; for(int i=num-1; i>=0; i--){ suf[i]=suf[i+1]*bit[i+1]; } } int solve(int state){ memset(dp, 0, sizeof(dp)); get_suf(); int cnt[20]; memset(cnt, 0, sizeof(cnt)); for(int i=0; i<=num; i++){ cnt[i]=1; dp[get_state(cnt)][i]=bit[i]-1;///dp[state][j]表示在state状态下,排列是以j为最后一个数字 cnt[i]=0; } for(int k=1; k<=state; k++){ get_cnt(k, cnt); for(int i=0; i<=num; i++){ if(cnt[i]==0) continue;//如果i位没有选数就构不成以i为结尾就跳过;这里表示在state状态下最后选的是i组的数,然后接下来枚举j作为新状态结尾,这个j和i互质; for(int j=0; j<=num; j++){ if(!prime[i][j]||cnt[j]>=bit[j]-1) continue;//如果i,j组数不互质或者j组无数可选了,就不选j组数;细心的朋友会发现0组咋办?即如果之前选了1,此时还能选19,所以prime[0][0]是等于1的; int s=k+suf[j]; dp[s][j]=(dp[s][j]+dp[k][i]*(bit[j]-cnt[j]-1)%mod)%mod;///新状态就是旧状态乘以j的剩余数量,剩余x个那肯定是结尾有x种搭配嘛 } } } int ans=0; for(int i=0; i<=num; i++){ ans=(ans+dp[state][i])%mod; } return ans%mod; } int main(){ int T; scanf("%d", &T); init(); while(T--){ int n; scanf("%d%d", &n, &mod); num=0; for(int i=1; i<=n; i++){ num=max(num, min(i, group[i]));///取出有多少种不同的集合,例如 0, 11,2,那么num=3; } int cnt[20]; memset(cnt, 0, sizeof(cnt)); for(int i=1; i<=n; i++){ cnt[group[i]]++; } for(int i=0; i<=num; i++){ bit[i]=cnt[i]+1;///因为我们有可不选这个状态,所以bits就应该cnt+1, } printf("%d ", solve(get_state(cnt))); } return 0; }