Crime HDU - 4623(状压DP,不同进制转换)

 

题目链接: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\n", solve(get_state(cnt)));
	}
	return 0;
}

  

posted on 2020-03-03 09:08  师姐的迷弟  阅读(128)  评论(0编辑  收藏  举报

导航