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\n",i);
return 0;
}
return 0;
}
/*
如果一个数可以被其他数表示 就把它换掉
update:可能出现都无法互相表示
所以找一个数 把它去掉 然后可以表示出来的数最多 我们就去掉那个数
*/
转载请注明出处,有疑问欢迎探讨
博主邮箱 2775182058@qq.com