【CF500F】New Year Shopping
题目
题目链接:https://codeforces.com/contest/500/problem/F
有 \(n\) 种商品,第 \(i\) 种商品的价格是 \(c_i\) ,购买后可以增加 \(h_i\) 的快乐指数,将于第 \(t_i\) 天上市。商品的保质期为 \(p\) 天,过期后不能再购买,即第 \(i\) 种商品只能在第 \(t_i\) 天到第 \(t_i+p-1\) 天之间购买,每种商品只能购买一次。
有 \(q\) 个询问,每次给定两个整数 \(a,b\) ,求在第 \(a\) 天去购物,最多使用 \(b\) 元的情况下可以得到的最大快乐指数。询问之间互不干扰。
\(n\leq 3\times 10^4\),\(q\leq 2\times 10^4\),所有价格 \(\leq 3\times 10^4\),所有时间和快乐指数 \(\leq 2\times 10^4\)。
思路
这道题似乎有很多做法,但是我只能想到卑微的 \(O(nq\log n)\)。
因为所有物品持续的时间都是一样的,所以按照上架时间排序后,每次的询问就是把一段区间的物品拿出来做 01 背包。
也就是需要维护一个队列,然后在某一时刻查询队列内所有物品的 01 背包。
而一个队列可以用两个栈 \(s1,s2\) 来实现:插入物品就往 \(s2\) 插入;删除物品就在 \(s1\) 删除;当 \(s1\) 为空时,就把 \(s2\) 所有元素依次弹出插入 \(s1\)。
然后维护两个栈中每一个元素到栈底的背包。按照时间离线询问,然后只需要合并一下两个栈栈顶的背包即可。
时间复杂度 \(O(nq)\)。
实现上不需要真的开栈,维护区间 \([l,mid]\) 为第一个栈的元素,\([mid+1,r]\) 为第二个栈的元素即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=4010,M=20010;
int n,p,Q,l,r,mid,f[N][N],ans[M];
struct node1
{
int w,v,t;
}a[N];
struct node2
{
int m,t,id;
}b[M];
bool cmp1(node1 x,node1 y)
{
return x.t<y.t;
}
bool cmp2(node2 x,node2 y)
{
return x.t<y.t;
}
void insert(int x,int *g,int *h)
{
memcpy(g,h,sizeof(f[0]));
for (int i=4000;i>=a[x].w;i--)
g[i]=max(g[i],g[i-a[x].w]+a[x].v);
}
int main()
{
scanf("%d%d",&n,&p);
for (int i=1;i<=n;i++)
scanf("%d%d%d",&a[i].w,&a[i].v,&a[i].t);
scanf("%d",&Q);
for (int i=1;i<=Q;i++)
{
scanf("%d%d",&b[i].t,&b[i].m);
b[i].id=i;
}
sort(a+1,a+1+n,cmp1);
sort(b+1,b+1+Q,cmp2);
l=1; mid=r=0;
for (int t=1,i=1;t<=20000;t++)
{
for (;r<n && a[r+1].t==t;r++)
insert(r+1,f[r+1],f[(r==mid)?0:r]);
while (l<=mid && a[l].t+p==t) l++;
if (l>mid)
{
mid=r;
for (int j=mid;j>=l;j--)
insert(j,f[j],f[(j==mid)?0:j+1]);
}
for (;i<=Q && b[i].t==t;i++)
for (int j=0;j<=b[i].m;j++)
{
int res1=(l<=mid) ? f[l][j] : 0;
int res2=(mid<r) ? f[r][b[i].m-j] : 0;
ans[b[i].id]=max(ans[b[i].id],res1+res2);
}
}
for (int i=1;i<=Q;i++)
printf("%d\n",ans[i]);
return 0;
}