NOI2015day1寿司晚宴 状压DP

NOI2015day1寿司晚宴

测试点1~3

这个写法不唯一。可以2^30枚举之后打表。也可以dp。

定义状态 d p [ i ] [ c h s 1 ] [ c h s 2 ] dp[i][chs1][chs2] dp[i][chs1][chs2]表示已经决策了2~i这些数,第一个人选的数的质因数中有chs1(二进制数表示状态),第二个人选的数的质因数有chs2的方案数。

这里i可以用滚动数组,也可以用01背包的套路(倒序枚举)优化。

测试点4~5

发现对于大于50的质因数只会有一个数有。那么可以把这些数放在最后决策,它们对于其他点的决策时不影响的,它们决策的方案数就是3(给第一个人,给第二个人,都不给)。就是先进行一遍测试1~3的算法,然后再计算出2~n有多少个大于50的质因数。对答案乘上 3 c n t 3^{cnt} 3cnt即可。

如果写法不同有乘法的话要注意P是最大1e10,相乘会爆longlong。要手写一个高精乘法(压两位即可)。这里附上本人丑陋的代码。

long long Mul(long long a,long long b){//Base=100000
	long long num1=a/Base,num2=a%Base,num3=b/Base,num4=b%Base;
	num[1]=num[2]=num[3]=num[4]=0;
	num[4]=1ll*num2*num4;
	if(num[4]>=Base)num[3]+=num[4]/Base,num[4]%=Base;
	num[3]+=1ll*num1*num4+num2*num3;
	if(num[3]>=Base)num[2]+=num[3]/Base,num[3]%=Base;
	num[2]+=1ll*num1*num3;
	if(num[2]>=Base)num[1]+=num[2]/Base,num[2]%=Base;
	long long ans=0;
	for(int i=1;i<=4;i++){
		ans=1ll*ans*Base+num[i];
		ans%=P;
	}
	return ans;
}

正解

因为500以内的质数很多,故我们如果再用上面的算法就不行了。但是可以发现500以内的每个数只有一个或者没有大于 500 \sqrt{500} 500 的质因子。对于一个大于 500 \sqrt{500} 500 的质因子K,我们把包含这个K的倍数的集合看做 S K S_K SK,对于这个集合里的数,只能有一个人取这个集合里的数(可以取任意个)。也可以两个人都不取。当然还要满足小于 500 \sqrt{500} 500 的质因子没被取重。

于是我们这里定义状态 F [ K ] [ o p ] [ c h s 1 ] [ c h s 2 ] F[K][op][chs1][chs2] F[K][op][chs1][chs2]已经决策了K个这个集合里的数,表示op这个人取了这个集合,第一个人取的数的质因数中有chs1,第二个人取的数的质因数有chs2的方案数。F数组内部的转移和dp数组转移类似。这里的K可以用倒序枚举消掉。

F数组的初值就是dp数组。处理完之后再传回dp数组。也就是 d p [ c h s 1 ] [ c h s 2 ] = F [ 0 ] [ c h s 1 ] [ c h s 2 ] + F [ 1 ] [ c h s 1 ] [ c h s 2 ] − d p [ c h s 1 ] [ c h s 2 ] dp[chs1][chs2]=F[0][chs1][chs2]+F[1][chs1][chs2]-dp[chs1][chs2] dp[chs1][chs2]=F[0][chs1][chs2]+F[1][chs1][chs2]dp[chs1][chs2]。减去 d p [ c h s 1 ] [ c h s 2 ] dp[chs1][chs2] dp[chs1][chs2]是因为对于两人都不选这个集合的数的方案算了两次,要减掉。

然后继续用上述方法处理剩下的集合。

对于那些没有大于 500 \sqrt{500} 500 的质因子的数,就是按照测试点1~3的方法直接dp即可。这样得到的dp数组就相当于后面处理时的初始数组。

这里转移有个优化,因为(chs1&chs2)==0故可以通过枚举I^chs1(I就是 2 c n t − 1 2^{cnt}-1 2cnt1,cnt是小于 500 \sqrt{500} 500 的质数的个数)的子集来枚举chs2。复杂度就只有 O ( n ∗ 3 8 ) O(n*3^{8}) O(n38),但是常数会使复杂度变大。

代码

#include
#define M 505
using namespace std;
bool cur1;
int n;
long long P;
void Add(long long &x,long long y){
	x=x+y;
	if(x>=P)x-=P;
	if(x<0)x+=P;
}
struct node{
	int x,chs;
	bool operator <(const node &_)const{
		return x<_.x;
	}
}A[M];
int Prime[M],n_p;
void Init(){//预处理每个数的质因子 
	n_p=0;
	for(int i=2;i*i<=500;i++){
		bool flag=true;
		for(int j=2;j*j<=i;j++)if(i%j==0){flag=false;break;}
		if(flag)Prime[++n_p]=i;
	}
	for(int i=2;i<=n;i++){
		int chs=0,x=i;
		for(int j=1;j<=n_p;j++){
			if(x%Prime[j]==0){
				while(x%Prime[j]==0)x/=Prime[j];
				chs|=1<<(j-1);
			}
		}
		A[i]=(node){x,chs};//x表示大于根号500的质因子,x==1时说明没有,chs是小于根号500的质因子的集合。 
	}
}
long long dp[1<<8][1<<8],F[2][1<<8][1<<8];
void Solve(){
	memset(dp,0,sizeof(dp));
	Init();
	int I=(1<<8)-1;
	dp[0][0]=1;
	sort(A+2,A+n+1);//按照大于根号500的质因子的大小排序。 
	for(int i=2;i<=n;i++){
		if(A[i].x==1){//这个数没有大于根号500的质因子 
			for(int chs1=I;chs1>=0;chs1--){//枚举第一个人取的数的质因子集合 
				int II=I^chs1;
				for(int chs2=II;;chs2=(chs2-1)&II){//枚举子集 
					if((chs2&A[i].chs)==0)Add(dp[chs1|A[i].chs][chs2],dp[chs1][chs2]);//这个数给第1个人 
					if((chs1&A[i].chs)==0)Add(dp[chs1][chs2|A[i].chs],dp[chs1][chs2]);//这个数给第2个人 
					if(!chs2)break;
				}
			}
			continue;
		}
		if(i==2||A[i].x!=A[i-1].x){//开一个新的块,处理这一个集合的数 
			memcpy(F[0],dp,sizeof(dp));
			memcpy(F[1],dp,sizeof(dp));
		}
		for(int chs1=I;chs1>=0;chs1--){
			int II=I^chs1;
			for(int chs2=II;;chs2=(chs2-1)&II){//这里的转移和上面相同 
				if((chs2&A[i].chs)==0)Add(F[0][chs1|A[i].chs][chs2],F[0][chs1][chs2]);
				if((chs1&A[i].chs)==0)Add(F[1][chs1][chs2|A[i].chs],F[1][chs1][chs2]);
				if(!chs2)break;
			}
		}
		if(i==n||A[i].x!=A[i+1].x){
			for(int chs1=I;chs1>=0;chs1--){
				int II=I^chs1;
				for(int chs2=II;;chs2=(chs2-1)&II){
					dp[chs1][chs2]=(F[0][chs1][chs2]+F[1][chs1][chs2]-dp[chs1][chs2])%P;
					dp[chs1][chs2]=(dp[chs1][chs2]+P)%P;
					if(!chs2)break;
				}
			}
		}
	}
	long long ans=0;
	for(int chs1=I;chs1>=0;chs1--){
		int II=I^chs1;
		for(int chs2=II;;chs2=(chs2-1)&II){
			Add(ans,dp[chs1][chs2]);//统计答案 
			if(!chs2)break;
		}
	}
	printf("%lld\n",ans);
}
bool cur2;
int main(){
	//printf("%lf\n",(&cur2-&cur1)/1024.0);
	scanf("%d%lld",&n,&P);
	Solve();
	return 0;
}

你可能感兴趣的:(DP,状压DP)