luogu P3826 [NOI2017]蔬菜

luogu

那个第一次购买有\(s_i\)奖励,可以看成是多一种蔬菜\(i+n\),权值为\(w_i+s_i\),每天减少量\(x\)为0个,保质期\(\lceil\frac{c_i}{x_i}\rceil\),数量为1的蔬菜,同时要把原来的\(c_i\)减一

现在考虑只有一组询问,我们贪心的想,应该先把价值最高的给卖了.所以按照权值从大到小排序.然后当前这种菜显然能在保质期期限内堆在后面卖就在后面卖,这样对后面保质期段的菜更优,那么就是从保质期那天开始往前推,记录能放的菜的数量,每天能放就放,还有就是每往前一天能卖的菜数量增加\(x_i\).为了保证复杂度,应该在这种菜后面没有增加量的时候退出,并且要跳过中间过程卖菜数量满的一些天,这个可以并查集实现,每个点记录这个点往前最近的能卖菜的一天

然后考虑多组询问,先把最大天数的答案求出来,然后从\(i+1\)天的答案推出第\(i\)天的答案.如果后一天总共卖菜的数量\(>i*m\),那么就把卖出去的菜中权值最小的若干个丢掉.正确性,一种菜能在后面的天卖出去,那就更能在前面的天卖出去,所以倒推是合法的

#include<bits/stdc++.h>
#define LL long long
#define uLL unsigned long long
#define db double

using namespace std;
const int N=2e5+10,lm=1e5;
LL rd()
{
    LL x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*w;
}
int ff[N];
int findf(int x){return ff[x]==x?x:ff[x]=findf(ff[x]);}
int n,m,q,w[N],dt[N],rs[N],xx[N],sq[N],sl[N];
LL an[N],smy[N*10],ts;
int gnm(int i,int j){return rs[i]+xx[i]*(dt[i]-j);}
bool cmp(int aa,int bb){return w[aa]>w[bb];}

int main()
{
	n=rd(),m=rd(),q=rd();
	for(int i=1;i<=n;++i)
	{
		w[i]=rd();
		int sd=rd(),nm=rd();
		xx[i]=rd();
		dt[i]=xx[i]?(nm+xx[i]-1)/xx[i]:lm+1;
		rs[i]=dt[i]<=lm?(nm-1)%xx[i]+1:(xx[i]?xx[i]:nm);
		dt[i]=min(dt[i],lm);
		w[i+n]=w[i]+sd;
		dt[i+n]=dt[i],rs[i+n]=1,--rs[i];
	}
	for(int i=1;i<=lm;++i) ff[i]=i,sl[i]=m;
	for(int i=1;i<=n+n;++i) sq[i]=i;
	sort(sq+1,sq+n+n+1,cmp);
	for(int i=1;i<=n+n;++i)
	{
		int x=sq[i],nw=findf(dt[x]),bb=0;
		while(nw&&bb<gnm(x,1))
		{
			int aa=gnm(x,nw)-bb,py=min(aa,sl[nw]);
			an[lm]+=1ll*py*w[x],bb+=py,sl[nw]-=py;
			while(py--) smy[++ts]=w[x];
			if(!sl[nw]) ff[nw]=nw-1;
			nw=findf(nw-1);
			if(x>n) break;
		}
	}
	sort(smy+1,smy+ts+1);
	reverse(smy+1,smy+ts+1);
	for(int i=lm-1;i;--i)
	{
		an[i]=an[i+1];
		while(ts>i*m) an[i]-=smy[ts--];
	}
	while(q--) printf("%lld\n",an[rd()]);
    return 0;
}
posted @ 2019-09-22 22:32  ✡smy✡  阅读(129)  评论(0编辑  收藏  举报