BalticOI 2010 Candies【dp】

题目链接

题目解析

首先考虑(P)怎么求。

刚开始想的如果一个数可以被其它数凑出来就不要它,这很对,但不能拿来算答案。

考虑到如果(Q)足够大,那么可以凑出来的数能达到去掉(P)可以凑出来的数的两倍。

所以答案就是去掉它,剩下的数可以凑成的数的个数最多。当然,个数相同取最小。

这个怎么求呢?我们可以参考[消失之物]的做法,做一个回退背包,在(O(n^2B))的时间复杂度内可以求出(P)

再来考虑(Q)

如果我们要把凑出来的数加个倍,那么(Q)加上集合内的一些数不能用来去表示已经存在了的数。

也就是,不存在子集(S,T)满足

(sum_{i∈S}B_i=sum_{j∈T}B_j+Q)

移项

(Q=sum_{i∈S}B_i-sum_{j∈T}B_j)

那么这是一个体积可以为负数的背包,我们要求的(Q)是最小的不能被表示出来的数。

另外需要注意一点的是,做背包的时候,状态当然不能用(0/1)表示是否能凑出,因为这样没法回退,所以需要表示方案数。但是方案数是指数级别的,所以可以模一个大质数,我们只需要知道方案数是否为零即可,而模一个大质数,碰撞概率是很小的(大概


►Code View

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define N 105
#define M 700005
#define MOD 998244353
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
	return f*x;
}
int n,b[N];
int f[M<<1],g[M<<1],s=0;
int ans=0,id=1;//如果没有 随便取一个 不过似乎没有这种情况 
int main()
{
	//freopen("candies.in","r",stdin);
	//freopen("candies.out","w",stdout);
	n=rd();
	for(int i=1;i<=n;i++)
		b[i]=rd();
	f[0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=s+b[i];j>=b[i];j--)
			f[j]=(f[j]+f[j-b[i]])%MOD;
		s+=b[i];
	}
	for(int i=1;i<=n;i++)
	{//去掉i做一个背包回退 
		int cnt=0;//多少个数字可以表示 
		for(int j=0;j<=s;j++)
		{
			if(b[i]>j) g[j]=f[j];
			else g[j]=(f[j]-g[j-b[i]]+MOD)%MOD;
			if(g[j]) cnt++;
		}
		if(cnt>ans||(cnt==ans&&b[i]<b[id]))
			ans=cnt,id=i;
	}
	printf("%d ",b[id]);
	for(int j=0;j<=s;j++)
	{//想好了去掉id 得到它的背包状态(体积为正 
		if(b[id]>j) g[j]=f[j];
		else g[j]=(f[j]-g[j-b[id]]+MOD)%MOD;
	}
	//做一个体积为负数的背包(前面只算了正体积 这里把负体积的贡献加进去 
	for(int i=1;i<=n;i++)
	{
		if(i==id) continue;
		for(int j=0;j<=s+b[i]/*s-(-b[i])*/;j++)
			g[j]=(g[j]+g[j+b[i]/*j-(-b[i]])*/])%MOD;
		//由于体积是负数 所以j-(b[i])在更前面被转移 所以这里是正着枚举j 
	}
	for(int i=0;;i++)
		if(!g[i])
		{
			printf("%d
",i);
			return 0;
		}
	return 0;
}
/*
如果一个数可以被其他数表示 就把它换掉
update:可能出现都无法互相表示
所以找一个数 把它去掉 然后可以表示出来的数最多 我们就去掉那个数 
*/
原文地址:https://www.cnblogs.com/lyttt/p/14053251.html