Solution
先讲讲暴力怎么做。
显然可以费用流。设p=max{pi}p=max\{p_i\}p=max{pi},建ppp个点表示ppp天,对于一种蔬菜iii,设k=min(p,⌈cixi⌉)k=\min(p,\lceil {c_i\over x_i}\rceil)k=min(p,⌈xici⌉),对于这种蔬菜,源点向前k−1k-1k−1个点(前k−1k-1k−1天)连流量为xix_ixi,费用为aia_iai的边,向最后一天的点连流量为ci−(k−1)xi−1c_i-(k-1)x_i-1ci−(k−1)xi−1,费用为aia_iai的边和流量为111,费用为ai+sia_i+s_iai+si的边,然后对于每一天的点,向前一天的点连流量为infinfinf,费用为000的边,再向汇点连流量为mmm,费用为000的边。这样可以保证每天的可用蔬菜都是合法的。因为是多组询问,可以这样:询问从小到大排序,每次增加一些流量继续跑,在LOJ上获得68分的好成绩。
考虑优化。注意到第iii天选择的蔬菜肯定是第i+1i+1i+1天选择蔬菜的子集,所以这个费用流实际上是没有退流的,所以可以模拟费用流。考虑用一个堆维护当前可用的蔬菜,那么当前的这种蔬菜肯定是选择最晚过期的那一批流到对应的那天,感性地理解一下,能够流到前面几天的更多,所以肯定是流向尽量后,用一个堆+并查集维护就行了。
Code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=100010,N=100000;
const int inf=2147483647;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*f;
}
int n,m,k,f[Maxn],cnt[Maxn];LL ans=0,Ans[Maxn];
int ff(int x){return((f[x]==x)?x:f[x]=ff(f[x]));}
struct P{int a,s,c,x;}p[Maxn];
struct Q{int x,id;}q[Maxn];
bool cmp(Q a,Q b){return a.x<b.x;}
struct Node
{
int v,id;
Node(int _v,int _id){v=_v,id=_id;}
};
bool operator<(Node a,Node b){return a.v<b.v;}
priority_queue<Node>que;
int main()
{
n=read(),m=read(),k=read();
for(int i=1;i<=n;i++)
{
p[i].a=read(),p[i].s=read(),p[i].c=read(),p[i].x=read();
que.push(Node(p[i].a+p[i].s,i));
}
for(int i=0;i<=N;i++)f[i]=i,cnt[i]=0;
for(int i=1;i<=N;i++)
{
int tmp=m;
while(tmp&&!que.empty())
{
Node t=que.top();que.pop();
int pos;
if(!p[t.id].x)pos=N;else pos=min(p[t.id].c/p[t.id].x+(p[t.id].c%p[t.id].x!=0),N);
int Pos=ff(pos);if(!Pos)continue;
cnt[Pos]++;if(cnt[Pos]==m)f[Pos]=ff(Pos-1);
ans+=t.v;p[t.id].c--;if(p[t.id].c)que.push(Node(p[t.id].a,t.id));
tmp--;
}
Ans[i]=ans;
}
while(k--)printf("%lld\n",Ans[read()]);
}