Luogu2150 寿司晚宴 - 状压dp - 质因数分解 -

题目链接:https://www.luogu.com.cn/problem/solution/P2150

题解:

  1. 30pts

\(n \leq 30\),如果\(x\)\(y\)互质,则必有至少一个公共质因子,而30以内质数共10个,对质因子状态进行压缩

\(dp[i][S1][S2]\)表示考虑到第 i 个数,甲使用的质因子集合为\(S1\),乙为\(S2\)的方案数,则有

\[dp[i][S1|cur[i]][S2] += dp[i-1][S1][S2]\ \ if\ \ S2\ bitand \ cur[i] == 0 \]

\[dp[i][S1][S2|cur[i]] += dp[i-1][S1][S2]\ \ if\ \ S1\ bitand \ cur[i] == 0 \]

\[dp[i][S1][S2] += dp[i-1][S1][S2](不选) \]

其中\(cur[i]\)表示\(i\)质因子的集合
最后对所有\(S1、S2\),统计\(dp[n][S1][S2]\)之和即可,需要使用滚动数组优化

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1e9;

int prm[]={2,3,5,7,11,13,17,19,23,29};
int n,p;
int dp[2][(1<<10)+5][(1<<10)+5];

signed main(){
	scanf("%d%d",&n,&p);
	int mod = p;
	dp[1][0][0] = 1;
	for(int i=2;i<=n;i++){
		memset(dp[i&1],0,sizeof dp[i&1]);
		int curi = 0,tmp = i;
		for(int j=0;j<10;j++)
			while(tmp%prm[j] == 0){tmp /= prm[j];curi |= 1<<j;}
		for(int S1=0;S1<=(1<<10)-1;S1++){
			for(int S2=0;S2<=(1<<10)-1;S2++)if((S1&S2) == 0){
				if((S2&curi) == 0)(dp[i&1][S1|curi][S2] += dp[i&1^1][S1][S2])%=mod;
				if((S1&curi) == 0)(dp[i&1][S1][S2|curi] += dp[i&1^1][S1][S2])%=mod;
				( dp[i&1][S1][S2] += dp[i&1^1][S1][S2] ) %= mod;
			}
		}
	}
	int ans=0;
	for(int S1=0;S1<=(1<<10)-1;S1++){
		for(int S2=0;S2<=(1<<10)-1;S2++)if((S1&S2) == 0){
			(ans += dp[(n)&1][S1][S2])%=mod;
		}
	}
	printf("%d\n",ans);

	return 0;
}


  1. 100pts

\(n\leq 500\),考虑到\(> \sqrt{n}\)的质因子只有1个,我们只需要把这个质因子拿出来单独考虑。\(\sqrt{500}<23\),因此对2、3、5、7、11、13、17、19这8个质数拿出来,跑一遍30pts的状压,再考虑\(\geq 23\)的部分

对于一个大质因数,显然只可能被二者中至多一个选,我先预处理出有哪些数,他们对应的大质因数是同一个,并利用排序将他们放在一起。

对于一段大质因数相同的序列,我们再对这一小段dp。记\(f[i][S1][S2]\)表示钦定大质因数只能甲选(甲可能不选可能选,但乙一定不选),考虑到第\(i\)个位置(排序之后),已经利用的小质因数集合甲为\(S1\)乙为\(S2\),所对应的方案数。对于当前的大致运输算完之后再对下一个大质因数计算,以此类推
考虑\(f、g\)的转移(记\(curS\)为当前数的小质因数的集合):

\[f[i][S1|curS][S2] += f[i-1][S1][S2]\ \ if \ \ S2 \ bitand \ curS=0 \]

\[g[i][S1][S2|curS] += g[i-1][S1][S2]\ \ if \ \ S1 \ bitand \ curS=0 \]

\[f[i][S1][S2] += f[i-1][S1][S2] (\ g[][][]\ 同理) \]

注意这是对当前大质数因子的统计答案,我们还需要把她加回\(dp[][][]\)中(注意\(dp[][][]\)原本的意义就是计算没有大质因子的情况,我们把所有大质数因子的情况都加回,就得到了答案)

\(dp[n][S1][S2] = f[n][S1][S2] + g[n][S1][S2] - dp[n][S1][S2]\)(减去是因为有大质因数两个人都不选的情况被算了2次)

是否有重复呢?注意到从一个质因数往下一个质因数推的时候,\(dp[][][]\)内对应的结果一定不包含下一个质因数的情况,因此每次都相当于往里面加一个大质数因子,没有重复

我先把小质数因子的情况求出来,再求解大质数因子,直观但代码比较麻烦,而且滚动数组这块很容易错。

小心别爆int!!

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1e9,maxn=505;

int n,p;
int prm[]={2,3,5,7,11,13,17,19};
struct node{
	int num, S;
	int mxp;
	node(){mxp=0;} 
}a[maxn];
int cmp(node a,node b){return a.mxp < b.mxp;}
int dp[2][(1<<8)+5][(1<<8)+5];
int f[2][(1<<8) +5][(1<<8) + 5];
int g[2][(1<<8) +5][(1<<8) + 5];

signed main(){
//	freopen("2150-2.out","w",stdout);
	scanf("%d%d",&n,&p);int mod=p;
	for(int i=2;i<=n;i++){
		int cur = i;
		a[i-1].num = i;
		for(int j=0;j<8;j++){
			while(cur%prm[j] == 0){
				cur /= prm[j];
				a[i-1].S |= (1<<j);
			}
		}
		a[i-1].mxp = cur;
	}
	sort(a+1,a+n,cmp);
	int t=n-1;
	for(int i=1;i<=n-1;i++)if(a[i].mxp!=1){t=i-1;break;} 
	dp[0][0][0] = 1;
	for(int i=1;i<=t;i++){
		memset(dp[i&1],0,sizeof dp[i&1]);
		int curi = a[i].S;
		for(int S1=0;S1<=(1<<8)-1;S1++){
			for(int S2=0;S2<=(1<<8)-1;S2++)if((S1&S2) == 0){
				if((S2&curi) == 0)(dp[i&1][S1|curi][S2] += dp[i&1^1][S1][S2])%=mod;
				if((S1&curi) == 0)(dp[i&1][S1][S2|curi] += dp[i&1^1][S1][S2])%=mod;
				( dp[i&1][S1][S2] += dp[i&1^1][S1][S2] ) %= mod;
			}
		}
	}
	int lst_dp = t;
	
	for(int i=t+1;i<=n-1;i++){
		if(a[i].mxp != a[i-1].mxp){
			memcpy(f[i&1^1],dp[lst_dp&1], sizeof f[i&1^1]);
			memcpy(g[i&1^1],dp[lst_dp&1], sizeof g[i&1^1]);
		}
		memset(f[i&1],0,sizeof f[i&1]), memset(g[i&1],0,sizeof g[i&1]);	
	
		for(int S1=0;S1<=(1<<8)-1;S1++){
			for(int S2=0;S2<=(1<<8)-1;S2++)if((S1&S2)==0){
				if((S1&a[i].S) == 0)( g[i&1][S1][S2|a[i].S] += g[i&1^1][S1][S2] ) %= mod;
				if((S2&a[i].S) == 0)( f[i&1][S1|a[i].S][S2] += f[i&1^1][S1][S2] ) %= mod;
				(f[i&1][S1][S2] += f[i&1^1][S1][S2]) %= mod;
				(g[i&1][S1][S2] += g[i&1^1][S1][S2]) %= mod;
			}
		}
		if(a[i].mxp != a[i+1].mxp || i == n-1){
			for(int S1=0;S1<=(1<<8)-1;S1++)
				for(int S2=0;S2<=(1<<8)-1;S2++)if((S1&S2) == 0)
					(dp[lst_dp&1][S1][S2] = (f[i&1][S1][S2] + g[i&1][S1][S2])%mod - dp[lst_dp&1][S1][S2] + mod )%= mod;//!!!! 
		}
	}
	int ans=0;
	for(int S1=0;S1<=(1<<8)-1;S1++)for(int S2=0;S2<=(1<<8)-1;S2++)if((S1&S2)==0)
		(ans += dp[lst_dp&1][S1][S2]) %= mod;
	printf("%d\n",ans);

	return 0;
}


posted @ 2022-08-12 00:04  SkyRainWind  阅读(8)  评论(0编辑  收藏  举报