[NOI2015] 寿司晚宴(状压DP)

本文介绍了如何使用DP解决洛谷竞赛中的一道题目,涉及30pts和100pts两种解法,分别是基于质因数互斥的状压DP和利用大质因子分组的优化策略。博主分享了关键思路和代码实现,展示了动态规划在数论问题中的应用。
摘要由CSDN通过智能技术生成

洛谷题目传送门

解题思路

首先考虑30pts的做法

因为两人选的数必须互质,因此可以想到只要维护二者没有共同质因子就行了

因为质因子个数很少,考虑状压DP

设 dp[S][T] 表示 A选了数的质因子状态为 S,B的质因子状态为 T

那么 dp[S|k][T]+=dp[S][T]      ((k&T)==0)

        dp[S][T|k]+=dp[S][T]      ((k&S)==0)

其中 k 表示 第 i 个数 的质因子状态,因为可以用滚动数组优化,因此不需要存储 i

代码也很好写

然后是100pts的做法

有一个性质是 数 n 只有最多一个 大于 根号n 的质因子(记为大质因子)

因此如果我们计算出每个数的大质因子,并将其排序

这样大质因子相同的数就在一块了,在每一块里,要么只能让A选,要么只能让 B选,要么都不选

因此我们仿照30pts的做法分别维护让A选和让B选的方案数,再合并起来就是答案

具体来说 设 f1[S|k][T]+=f1[S][T]

                   f2[S|k][T]+=f2[S][T]

然后每一个块算完之后

dp[S][T]=f1[S][T]+f2[S][T]-dp[S][T]

因为不选的方案数会记两次,因此需要减掉

复杂度O(x^{16}*n)

代码如下

PS:这是我通过的第一道DP黑题,纪念一下

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
struct node
{
	int v;
	int big;
	int S;
}num[510];
LL dp[260][260],f1[260][260],f2[260][260];
int p[10]={0,2,3,5,7,11,13,17,19};
int n,mod;
bool cmp(node x,node y)
{
	return x.big<y.big;
}
void resolve(int x) //求大质因子
{
	int tmp=num[x].v;
	num[x].big=-1;
	for(int i=1;i<=8;i++)
	{
		if(tmp%p[i]!=0) continue;
		num[x].S|=(1<<(i-1));
		while(tmp%p[i]==0) tmp/=p[i];
	}
	if(tmp!=1) num[x].big=tmp;
}
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
int main()
{
	freopen("dinner.in","r",stdin);
	freopen("dinner.out","w",stdout);
	n=read();
	mod=read();
	for(int i=2;i<=n;i++)
	{
		num[i-1].v=i;
		resolve(i-1);
	}
	sort(num+1,num+n,cmp);
	dp[0][0]=1;
	for(int i=1;i<n;i++)
	{
		if(i==1||num[i-1].big!=num[i].big||num[i].big==-1) //首先需要先把dp的值赋值给f1,f2
		{
			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((num[i].S&k)==0) f1[j|num[i].S][k]+=f1[j][k];
				if((num[i].S&j)==0) f2[j][k|num[i].S]+=f2[j][k];
				f1[j][k]=(f1[j][k]>=mod)?f1[j][k]%mod:f1[j][k];
				f2[j][k]=(f2[j][k]>=mod)?f2[j][k]%mod:f2[j][k];
				
			}
		}
		if(i==n-1||num[i].big!=num[i+1].big||num[i].big==-1)
		{
			for(int j=255;j>=0;j--)
			{
				for(int k=255;k>=0;k--)
				{
					if(j&k) continue;
					dp[j][k]=((f1[j][k]+f2[j][k])%mod+mod-dp[j][k])%mod;
				}
			}
		}
	}
	LL ans=0;
	for(int i=0;i<=255;i++)
	{
		for(int j=0;j<=255;j++)
		{
			if((j&i)==0&&dp[i][j])
			ans=(ans+dp[i][j])%mod;
		}
	}
	cout<<ans;
	return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值