【洛谷2150】[NOI2015] 寿司晚宴(状压DP)

点此看题面

大致题意:\(2\sim n\)\(n-1\)个数,你可以选出其中任意个数分成两个集合,求使得第一个集合中的每一个数与第二个集合中每一个数两两互质的方案数。

转化

考虑两两互质,就相当于是每个质因子只能出现在至多一个集合中。

于是一个暴力的\(DP\),就是设\(f_{i,j}\),其中\(i,j\)状压表示两个集合中分别是否存在某种质因子。(显然这可以压缩成三进制,但没必要)

导致这种做法复杂度大的根本原因,就是\(1\sim500\)的质数个数很多。

但这时就有一个基本套路:每个数的质因子中最多只有一个大于根号\(n\)。而小于根号\(n\)(约等于\(22\))的质数只有\(8\)个。

因此,我们就可以用与上面同样的方式设立状态,只不过\(i,j\)都在\(0\sim 255\)的范围内。

然后每次我们枚举一段大于根号\(n\)的质因子相同的数,那么它们只可能放入一个集合中。

为了方便转移,设\(g1_{i,j},g2_{i,j}\)分别表示把这些数放入第一个集合与第二个集合中得到的\(DP\)数组。

最终可以更新\(f_{i,j}\)\(g1_{i,j}+g2_{i,j}-f_{i,j}\),要减去\(f_{i,j}\)是因为\(g1\)\(g2\)会各自计算一次空集的情况,贡献重复了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,X,Pt,P[N+5],f[256][256],g1[256][256],g2[256][256];struct Data
{
	int p,s;I Data(CI x=0,CI y=0):p(x),s(y){}
	I bool operator < (Con Data& o) Con {return p<o.p;}
}s[N+5];
I bool IsP(CI x) {for(RI i=2;i*i<=x;++i) if(!(x%i)) return 0;return 1;}
int main()
{
	RI i;for(scanf("%d%d",&n,&X),i=2;i<=n;++i) IsP(i)&&(P[++Pt]=i);//求质数,直接暴力
	RI j;for(i=2;i<=n;++i)//预处理
	{
		for(j=1;j<=min(8,Pt);++j) !(i%P[j])&&(s[i].s|=1<<j-1);//状压小于根号n的质因子
		W(j<=Pt&&i%P[j]) ++j;j<=Pt&&(s[i].p=P[j]);//找出大于根号n的质因子
	}sort(s+2,s+n+1);//排序
	RI x,y;for(f[0][0]=1,i=2;i<=n&&!s[i].p;++i) for(x=255;~x;--x) for(y=255;~y;--y)//先DP最大质因子小于根号n的情况
		!(y&s[i].s)&&Inc(f[x|s[i].s][y],f[x][y]),!(x&s[i].s)&&Inc(f[x][y|s[i].s],f[x][y]);//放入第一个集合或第二个集合中两种转移
	W(i<=n)//DP最大质因子大于根号n的情况
	{
		j=i;W(s[i].p==s[j+1].p) ++j;for(x=255;~x;--x) for(y=255;~y;--y) g1[x][y]=g2[x][y]=f[x][y];//找出最大质因子相同的一段数
		for(;i<=j;++i) for(x=255;~x;--x) for(y=255;~y;--y)
			!(y&s[i].s)&&Inc(g1[x|s[i].s][y],g1[x][y]),!(x&s[i].s)&&Inc(g2[x][y|s[i].s],g2[x][y]);//g1表示放入第一个集合,g2表示放入第二个集合
		for(x=255;~x;--x) for(y=255;~y;--y) f[x][y]=((g1[x][y]+g2[x][y]-f[x][y])%X+X)%X;//更新f
	}
	RI t=0;for(x=255;~x;--x) for(y=255;~y;--y) Inc(t,f[x][y]);return printf("%d\n",t),0;//统计答案
}
posted @ 2020-06-19 17:48  TheLostWeak  阅读(22)  评论(0编辑  收藏