3163: [Heoi2013]Eden的新背包问题

时间:2022-02-24 07:18:08

Description

“寄没有地址的信,这样的情绪有种距离,你放着谁的歌曲,是怎样的心心静,能不能说给我听。”
失忆的Eden总想努力地回忆起过去,然而总是只能清晰地记得那种思念的感觉,却不能回忆起她的音容笑貌。 记忆中,她总是喜欢给Eden出谜题:在 valentine’s day 的夜晚,两人在闹市中闲逛时,望着礼品店里精巧玲珑的各式玩偶,她突发奇想,问了 Eden这样的一个问题:有n个玩偶,每个玩偶有对应的价值、价钱,每个玩偶都可以被买有限次,在携带的价钱m固定的情况下,如何选择买哪些玩偶以及每个玩偶买多少个,才能使得选择的玩偶总价钱不超过m,且价值和最大。众所周知的,这是一个很经典的多重背包问题,Eden很快解决了,不过她似乎因为自己的问题被飞快解决感到了一丝不高兴,于是她希望把问题加难:多次 询问,每次询问都将给出新的总价钱,并且会去掉某个玩偶(即这个玩偶不能被选择),再问此时的多重背包的答案(即前一段所叙述的问题)。  
这下Eden 犯难了,不过Eden不希望自己被难住,你能帮帮他么?

Input

第一行一个数n,表示有n个玩偶,玩偶从0开始编号 
第二行开始后面的 n行,每行三个数 ai, bi, c i,分别表示买一个第i个玩偶需
要的价钱,获得的价值以及第i个玩偶的限购次数。 
接下来的一行为q,表示询问次数。 
接下来q行,每行两个数di. ei表示每个询问去掉的是哪个玩偶(注意玩偶从0开始编号)以及该询问对应的新的总价钱数。(去掉操作不保留,即不同询问互相独立)

Output

输出q行,第i行输出对于第 i个询问的答案。

Sample Input

5
2 3 4
1 2 1
4 1 2
2 1 1
3 2 3
5
1 10
2 7
3 4
4 8
0 5

Sample Output

13
11
6
12
4

HINT

一共五种玩偶,分别的价钱价值和限购次数为 (2,3,4), (1,2,1), (4,1,2), (2,1,1),(3,2,3)。五个询问,以第一个询问为例。第一个询问表示的是去掉编号为1的玩偶,且拥有的钱数为10时可以获得的最大价值,则此时剩余玩偶为(2,3,4),(4,1,2),(2,1,1),(3,2,3),若把编号为0的玩偶买4个(即全买了),然后编号为3的玩偶买一个,则刚好把10元全部花完,且总价值为13。可以证明没有更优的方案了。注意买某种玩偶不一定要买光。 数据满足1 ≤ n ≤ 1000, 1 ≤ q ≤ 3*10的5次方 , 1 ≤  ai、bi、c i ≤ 100, 0 ≤ d i < n,  0  ≤ei ≤ 1000。 

x-1,x+1——n中取最优解,我们把它分成两半,分配左边一定数目的钱,分配右边一定数目的钱,由于之前做过完全背包,我们可以直接得出左边和右边在一定钱数下的最优解,

相加后即为一种可能的答案,当然我们需要枚举分配的钱数在不同的可能性之间进行比较才能达到最终答案。

我采用了二进制优化来避免超限。

ps:这个程序时间复杂度在最后有可能达到O(3*10^8),但是数据水,所以过去了,还是建议看那些大神的题解。

具体看程序:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cstdio>
using namespace std;
int n,t,x,y,z,maxm,now,ans,q,d[],e[],w[],v[],l[],r[],f[][],f2[][];
int max(int a,int b)
{
return (a>b?a:b);
}
int main()
{
scanf("%d",&n);
now=;
for (int i=;i<=n;i++)
{
t=;
l[i]=now;//记录这种物品被分开后在背包中开始的位置
scanf("%d %d %d",&x,&y,&z);
while ((t*-)<z)//转换为01背包,并且进行二进制优化
{
w[now]=t*x;
v[now]=t*y;
t*=;
now+=;
}
w[now]=(z-t+)*x;
v[now]=(z-t+)*y;
r[i]=now;//记录这种物品被分开后在背包中最后的位置
now+=;
}
scanf("%d",&q);
for (int i=;i<=q;i++)
{
scanf("%d %d",&d[i],&e[i]);
d[i]+=;
if (e[i]>maxm) maxm=e[i];//所有钱数中的最大值
}
for (int i=;i<=now;i++)//正着做01背包
for (int j=maxm;j>=;j--)
if (j-w[i]>=) f[i][j]=max(f[i-][j],f[i-][j-w[i]]+v[i]);
else f[i][j]=f[i-][j];
for (int i=;i<=now/;i++)//把背包倒过来
{
t=w[i]; w[i]=w[now-i+];w[now-i+]=t;
t=v[i]; v[i]=v[now-i+];v[now-i+]=t;
}
for (int i=;i<=now;i++)//倒着做01背包
for (int j=maxm;j>=;j--)
if (j-w[i]>=) f2[i][j]=max(f2[i-][j],f2[i-][j-w[i]]+v[i]);
else f2[i][j]=f2[i-][j];
for (int i=;i<=q;i++)//开始回答问题
{
ans=;
for (int j=;j<=e[i];j++)//枚举分配给左边多少钱
ans=max(ans,f[l[d[i]]-][j]+f2[now-r[d[i]]][e[i]-j]);
//这个可以去推一下l[d[i]]-1表示被删物品的前一个物品在f背包中的结束位置,now-r[d[i]]表示被删物品的后一个物品在f2背包中的开始位置。
printf("%d\n",ans);
}
return ;
}

好啦