【洛谷1899】魔法物品
有意思的DP题
原题:
普通物品肯定直接卖了
令a原价,b为开后价,m为买卷的钱
b[i]-a[i]<=m的也直接卖了
接下来思考性质
1.开每个物品的花费是一样的
这个性质看上去比较吸引人
2.卖物品没有顺序,只需考虑开哪些物品
一开始想了个假解,就是忘了物品出售没有顺序的
3.开包一时爽,一直开包一直爽
本题的核心
需要注意到我们决定先开再卖的物品全部满足b[i]-a[i]>m
那么这些物品也满足b[i]>m
即如果开了一个物品,那么它的售价字词我们开下一个
结合性质1,只要先垫钱开一个物品,剩下的值得开的物品都安排上了
问题转化为垫够钱的前提下使损失最小
因为有些物品本来卖b[i],为了垫钱只能卖a[i]
背包DP
f初值为负无穷,但f[普通物品和不值得卖的物品价格和]=0
背包容量为m+max{a}(比这个大的花费肯定不优,因为把a最大的物品拿掉后仍然能垫够钱)
然后令物品重量为a[i],收益为a[i]-b[i],往里塞,表示拿这个物品去攒钱
最后检查m到m+max{a}中的最大值即可(因为权值都是负的,所以最大权值为最小损失)
总复杂度O(nm)
这题是我通过DP标签找到的,一开始想各种DP没有任何思路
因为有后效性,顺序枚举物品的话,后边没枚举到的物品是否直接卖会影响到前边的物品能不能攒够钱买卷
(我也不知道这个算不算后效性,反正先考虑前i个物品的DP搞不成)
最后居然是先发现性质,然后DP解决子问题
非常有意思
以及,这题输入数据真糟
还好我读入处理玩得6,不喜欢字符串处理的同学要被练了
代码:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int oo=1000000007; 5 int n,m; 6 int a[11000],b[11000]; 7 bool flg[11000]; 8 int f[21000]; 9 char s[11000]; int lth; 10 int main(){ 11 //freopen("ddd.in","r",stdin); 12 cin>>n>>m; 13 for(int i=1;i<=n;++i){ 14 //scanf("%d%d",&a[i],&b[i]); 15 char ch=getchar(); 16 while(ch=='\n'||ch=='\r') ch=getchar(); 17 lth=0; 18 while(ch!='\n'&&ch!='\r'){ 19 s[++lth]=ch; //注意顺序 20 ch=getchar(); 21 } 22 bool flg=false; 23 for(int j=1;j<=lth;++j){ 24 if(s[j]==' ') flg=true; 25 else if(!flg) a[i]=a[i]*10+s[j]-'0'; 26 else b[i]=b[i]*10+s[j]-'0'; 27 } 28 } 29 int bwl=0; 30 for(int i=1;i<=n;++i){ 31 flg[i]=(b[i]-a[i]<=m); 32 if(flg[i]) bwl+=a[i]; 33 } 34 if(bwl>=m){ 35 for(int i=1;i<=n;++i)if(!flg[i]) bwl+=b[i]-m; 36 printf("%d\n",bwl); 37 } 38 else{ 39 int M=20000; 40 for(int i=0;i<=M;++i) f[i]=-oo; 41 f[bwl]=0; 42 for(int i=1;i<=n;++i)if(!flg[i]) 43 for(int j=M;j>=a[i];--j) f[j]=max(f[j],f[j-a[i]]+a[i]-b[i]+m); //注意m 44 int mx=-oo; 45 for(int i=m;i<=M;++i) mx=max(mx,f[i]); 46 if(mx==-oo){ 47 for(int i=1;i<=n;++i)if(!flg[i]) bwl+=a[i]; 48 printf("%d\n",bwl); 49 } 50 else{ 51 for(int i=1;i<=n;++i)if(!flg[i]) bwl+=b[i]-m; //注意m 52 printf("%d\n",bwl+mx); 53 } 54 } 55 return 0; 56 }