• 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:可能出现都无法互相表示
    所以找一个数 把它去掉 然后可以表示出来的数最多 我们就去掉那个数 
    */
    
  • 相关阅读:
    [DP] Rod-cutting problem
    Dynamic Programming (DP) 问题总结
    [CC150] 八皇后问题
    [cc150] 硬币问题
    [cc150] 括号问题
    [CC150] Get all permutations of a string
    让Eclipse使用新版本的JRE
    Java中的数组问题
    慎用递归!
    cocos2d-x 添加背景音乐和音效-SimpleAudioEngine
  • 原文地址:https://www.cnblogs.com/lyttt/p/14053251.html
Copyright © 2020-2023  润新知