洛谷 P2150

我记得这是我 NOI 的某天晚上去自习室写的题,然后题解看不懂然后没电了,回寝室之后 AC 了,AC 了然后还是不会(

还有,洛谷咋这么喜欢关题解入口啊???

洛谷题目页面传送门

给定 (n),求有多少个集合有序二元组 ((S,T)) 满足 (S,Tsubseteq{2,3,cdots,n})(forall xin S,forall yin T,xperp y)。答案对 (p) 取模。

(nin[2,500],pinleft[1,10^{10} ight])

显然,不能打表

显然,条件可以转化为:(S,T) 分别的质因数集合没有交集。于是很容易想到状压 DP。

容易想到 (2) 个比较暴力的状压 DP:

  1. (dp_{i,j}) 表示考虑到数 (i)(S) 的质因数集合为 (j) 的方案数。很好转移,刷表即可;比较难的地方在于最后统计答案,注意到 (S,T) 的情况应该是一样的,所以答案就是 (sumlimits_{Acap B=varnothing}dp_{n,A}dp_{n,B})。时间复杂度 (mathrm O!left(n2^{pi(n)} ight))
  2. 不像上面那样绕弯子了,直白一点:(dp_{i,j,k}) 表示考虑到数 (i)(S,T) 的质因数集合分别为 (j,k) 的方案数。转移依然刷表;统计答案就直接统计了。时间复杂度 (mathrm O!left(n4^{pi(n)} ight))

写个程序随便算一下发现 (n=500)(pi(n)=95),无论哪种都是跑不过的。

注意到一个性质:一个数的所有质因数之积要小于等于原数。那么显然,(geqsqrt x)(x) 的质因数只有一个。于是我们可以利用根号分治的基本思想,将每个数的质因数分为两类:小质因数和大质因数。显然大质因数的数量在 (0)(1) 之间。把有大质因数的数,大质因数相同的分为一组;没有大质因数的数,每个数单独分为一组。这样显然,大质因数的限制仅在于每组之内,即每组最多只能有 (1) 个集合选数;然后带着这个限制,我们可以抛开大质因数只考虑小质因数了,考虑每组分别算,之后合并。小质因数最多只有 (pi(sqrt n)=8) 个,感觉很行。

考虑在小质因数范围内套用上面的暴力状压 DP。第 (1) 种是萎掉了,因为最后统计答案的时候还是要考虑大质因数,而我们没有把它们包含在状态里(所以你别看它复杂度小,其实局限性大。很多其他题也是这样的,标算往往是从比较 naive 的暴力优化过来的);于是只能用第 (2) 种。加上这里大质因数带来的特殊限制条件,原来转移的时候有 (3) 种贡献方式:不分、分 (S)、分 (T)。而现在不行了,可以重新设 (2) 个 DP 数组 (dp1,dp2),然后每个的转移只剩 (2) 种贡献方式。

接下来考虑如何合并两组。如果直接用两组 DP 数组最终状态直接进行合并的话,是四次方的复杂度,没有前途。不妨换一个思路,第二组从初始化 DP 数组的时候就把第一组的最终状态继承过来,最后随便容斥一下即可。这样对于每个数,都要有一个 (mathrm O!left(4^{pi(sqrt n)} ight)) 的 DP 数组,毛估估一下理论可过,但是 (mod) 运算写的丑的话会被卡常,其实可以优化。空间可以滚动数组滚成总 (mathrm O!left(4^{pi(sqrt n)} ight)) 的;时间上的话,注意到若 DP 数组两个维度有交集的话,一定是不合法的,直接不看,可以优化到 (mathrm O!left(n3^{pi(sqrt n)} ight)),但我比较懒,初始化的时候直接 memset 理论上复杂度没优化,但是大常数转移的时候优化了,可以通过。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
const int N=500;
int n,mod;
int dp[2][1<<8][1<<8];
int dp1[2][1<<8][1<<8],dp2[2][1<<8][1<<8];
vector<int> hav[N+1];
int id[N+1];
int mkmsk(vector<int> v,bool nobk=false){
	int res=0;
	for(int i=0;i+nobk<v.size();i++)res|=1<<id[v[i]];
	return res;
}
signed main(){
	id[2]=0;id[3]=1;id[5]=2;id[7]=3;id[11]=4;id[13]=5;id[17]=6;id[19]=7;
	cin>>n>>mod;
	for(int i=2;i<=n;i++){
		vector<int> v;
		int cpy=i;
		for(int j=2;j*j<=cpy;j++)if(cpy%j==0){
			v.pb(j);
			while(cpy%j==0)cpy/=j;
		}
		if(cpy>1)v.pb(cpy);
		if(v.size()&&v.back()>22)hav[v.back()].pb(mkmsk(v,true));
		else hav[i].pb(mkmsk(v));
	}
	int now=0;
	dp[0][0][0]=1;
	for(int i=1;i<=n;i++)if(hav[i].size()){
		now++;
		for(int j=0;j<1<<8;j++)for(int k=0;k<1<<8;k++)dp1[0][j][k]=dp2[0][j][k]=dp[now-1&1][j][k];
		for(int j=0;j<hav[i].size();j++){
			memset(dp1[j+1&1],0,sizeof(dp1[j+1&1])),memset(dp2[j+1&1],0,sizeof(dp2[j+1&1]));
			for(int k=0;k<1<<8;k++)for(int o=(1<<8)-1^k;~o;o=o?o-1&((1<<8)-1^k):-1)
				(dp1[j+1&1][k][o]+=dp1[j&1][k][o])%=mod,(dp1[j+1&1][k|hav[i][j]][o]+=dp1[j&1][k][o])%=mod,
				(dp2[j+1&1][k][o]+=dp2[j&1][k][o])%=mod,(dp2[j+1&1][k][o|hav[i][j]]+=dp2[j&1][k][o])%=mod;
		}
		for(int j=0;j<1<<8;j++)for(int k=(1<<8)-1^j;~k;k=k?k-1&((1<<8)-1^j):-1)
			dp[now&1][j][k]=((dp1[hav[i].size()&1][j][k]+dp2[hav[i].size()&1][j][k]-dp[now-1&1][j][k])%mod+mod)%mod;
	}
	int ans=0;
	for(int i=0;i<1<<8;i++)for(int j=(1<<8)-1^i;~j;j=j?j-1&((1<<8)-1^i):-1)(ans+=dp[now&1][i][j])%=mod;
	cout<<ans;
	return 0;
}
珍爱生命,远离抄袭!
原文地址:https://www.cnblogs.com/ycx-akioi/p/Luogu-P2150.html