【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;
}
posted @ 2022-01-02 22:33  stoorz  阅读(51)  评论(0编辑  收藏  举报