P2150 【NOI2015】寿司晚宴 状压DP+数学

题意:

两个人从 \(2\)\(n\)\(n-1\)个数字中各选一些(允许不选),使得选出来的两个集合中不存在一对数满足,\(x\in A,y\in B\)\(gcd(x,y)\ne 1\),求合法的分配方案数

数据范围:\(1\le n\le 500\)

分析:

题目可以转化为选出两个集合使得,每个集合的质因数集合没有交集

  • 30pt

\(n \le 30\) 的情况下质因数集合里的数不会超过10个,采用状压的方式,我们记\(f[i][j][k]\)表示考虑到第\(i\)个数时甲集合质因数集合状态为\(j\),乙集合质因数集合状态为\(k\)的方案数,预处理出\(n\)以内每个数质因数分解后的状态,转移方程就是

if( w&k == 0) f[i][j][k] += f[i-1][j|w][k];
if( w&j == 0) f[i][j][k] += f[i-1][j][k|w];

滚动数组优化掉一维,复杂度为\(O(n\times 2^{20})\)

  • 100pt

\(n\)变大之后质因数增多所以无法直接状压,但我们观察发现500这个数字给的很巧妙,因为500以内的每个数最多只有一个大于19的质因数,所以我们把每个数大于19的质因数拿出来记录一下,将大质因数相同的数放在一个集合里面,因为他们不能同时被甲乙选择,对于每一个集合内部,我们按照上面30pt的方法进行转移,具体来说就是记三个数组:

\(dp[i][j]\)表示当前两个质因数集合的状态为\(i,j\)

\(f1[i][j]\)表示让乙不选大质因数的情况下,两个质因数集合状态为\(i,j\)

\(f2[i][j]\)表示让甲不选大质因数的情况下,两个质因数集合状态为\(i,j\)

对于集合内部的转移:

if( w&j == 0) f1[i][j] += f1[i|w][j];
if( w&i == 0) f2[i][j] += f2[i][k|j];

对于集合之间的转移就是

dp[i][j]=f1[i][j]+f2[i][j]-dp[i][j]

减掉\(dp[i][j]\)是因为\(f2,f1\)都包含两人都不选的情况,会重复计算一次

答案就是统计所有的\(dp[i][j]\),复杂度为\(O(n\times 2^{16})\)

代码:

#include<bits/stdc++.h>

using namespace std;

namespace zzc
{
	int n,mod;
	int p[10]={0,2,3,5,7,11,13,17,19,0};
	int dp[300][300],f1[300][300],f2[300][300];
	
	struct node
	{
	    int val,big,s;
	    
	    void init()
		{
	        int tmp=val;
			big=-1;
	        for(int i=1;i<=8;i++)
			{
	            if(tmp%p[i]) continue;
	            s|=(1<<i-1);
	            while(tmp%p[i]==0) tmp/=p[i];
	        }
	        if(tmp!=1) big=tmp; 
	    }
	    
	}a[510];
	
	inline bool cmp(node a,node b)
	{
	    return a.big<b.big;
	}
	
	void work()
	{	
	    scanf("%d%d",&n,&mod);
	    for(int i=2;i<=n;i++) a[i-1].val=i,a[i-1].init();
	    sort(a+1,a+n,cmp);
	    dp[0][0]=1;
	    for(int i=1;i<n;i++)
		{
	        if(i==1||a[i].big!=a[i-1].big||a[i].big==-1)
			{
	            memcpy(f1,dp,sizeof(f1));
	            memcpy(f2,dp,sizeof(f2));
	        }
	        
	        for(int j=255;j>=0;j--)
			{
	            for(int k=255;k>=0;k--)
				{
	                if(j&k) continue;
	                if((a[i].s&j)==0) f2[j][k|a[i].s]=(f2[j][k|a[i].s]+f2[j][k])%mod;
	                if((a[i].s&k)==0) f1[j|a[i].s][k]=(f1[j|a[i].s][k]+f1[j][k])%mod;
	            }
	        }
	        
	        if(i==n-1||a[i].big!=a[i+1].big||a[i].big==-1)
			{
	            for(int j=0;j<=255;j++)
				{
	                for(int k=0;k<=255;k++)
					{
	                    if(j&k) continue;
	                    dp[j][k]=(f1[j][k]+(f2[j][k]+mod-dp[j][k])%mod)%mod;
	                }
	            }
	        }
	        
	    }
	    
	    long long ans=0;
	    for(int j=0;j<=255;j++)
		{
	        for(int k=0;k<=255;k++)
			{
	            if((j&k)==0&&dp[j][k]) ans=(ans+dp[j][k])%mod;
	        }
	    }
	    printf("%lld\n",ans);
	}
}

int main()
{
	zzc::work();
	return 0;
}
posted @ 2020-10-13 10:36  youth518  阅读(88)  评论(0编辑  收藏  举报