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

题意:

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

数据范围: 1 ≤ n ≤ 500 1\le n\le 500 1n500

分析:

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

  • 30pt

n ≤ 30 n \le 30 n30 的情况下质因数集合里的数不会超过10个,采用状压的方式,我们记 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑到第 i i i个数时甲集合质因数集合状态为 j j j,乙集合质因数集合状态为 k k k的方案数,预处理出 n n 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 × 2 20 ) O(n\times 2^{20}) O(n×220)

  • 100pt

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

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

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

f 2 [ i ] [ j ] f2[i][j] f2[i][j]表示让甲不选大质因数的情况下,两个质因数集合状态为 i , j 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]

减掉 d p [ i ] [ j ] dp[i][j] dp[i][j]是因为 f 2 , f 1 f2,f1 f2,f1都包含两人都不选的情况,会重复计算一次

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

代码:

#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值