题解【P3826 [NOI2017] 蔬菜】
command_block · · 题解
更严谨的正确性分析。
更自然的非启发式思路。(然而也有点难想)
更强的扩展性。(然而也没做过几个题)
更多的草稿纸。
广告 : 模拟费用流小记 (建议阅读)
题意 : 有
每天可以卖掉
首先解释一下题意。
同种蔬菜中,每棵蔬菜都有自己独立的变质时间,在不出售的情况下,效果是每天有
-
正确理解 : 每天会有
3 棵菜变质,则变质时间为\{1,1,1,2,2,2,3,3,3...\} 。若第一天卖掉了两棵[1] ,则只有一棵[1] 会变质。 -
错误理解 : 无论怎么卖,剩余的该种蔬菜都会变质
3 个。
也可以理解为,将蔬菜再按照过期时间分类为不同类别的菜。
考虑时间倒流,问题变为 : 每天都会加入一些菜(原来会变质的菜),菜可以无限期保存,但每天最多出售
为第
连边
为第
建边
在菜
(不难发现,任意一种没有卖掉“特殊菜”情况都可以转化为卖掉“特殊菜”的情况而增加收益,算法一定不会错过奖励)
然后求解最大费用任意流。
首先观察建图 :
只有源边有权值,其余边的权值均为
考虑使用 “增量-最大费用任意流” 模型。
时间倒流后,原来限制只能在前
从后向前依次加入每一天的出售点(这样才能符合原来的时间顺序),观察会新形成怎样的“源汇路”或“环” :
图
图
图
图
图
综上,可能的更新操作中,所有 S 的出边不会退流。
因此,我们的决策实际上是非常简单的。形式化地,在未经时光倒流的原问题中,前
有了这个性质,考虑先求出前
若前
对于第
那么将
至于如何求出前
现在已经没有时序的限制,我们可以按照任意顺序处理费用流图。
(我们将思路回到时光倒流后的形式)
图
图
图
图
在同一种菜中,出售那颗的获益都是一样的,故使用堆维护各种菜即可。当售空时,则从堆中取出。
当加入菜时,若原来这种菜售空,则将其加入堆。这需要维护售空且有持续输入的菜的集合。
(还有一点关于
复杂度
#include<algorithm>
#include<cstdio>
#include<queue>
#define pb push_back
#define Pr pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define ll long long
#define MaxN 100500
using namespace std;
int a[MaxN],s[MaxN],tot[MaxN],d[MaxN],c[MaxN];
int n,lim,m,t[MaxN],tim;
ll ans[MaxN*10];
vector<int> g[MaxN];
int st[MaxN],tn;
priority_queue<Pr> q;
int main()
{
scanf("%d%d%d",&n,&lim,&m);
for (int i=1;i<=n;i++)
scanf("%d%d%d%d",&a[i],&s[i],&tot[i],&d[i]);
for (int i=1;i<=m;i++){
scanf("%d",&t[i]);
tim=max(tim,t[i]);
}
for (int i=1;i<=n;i++)
if (!d[i])g[tim].pb(i);
else g[min((tot[i]+d[i]-1)/d[i],tim)].pb(i);
for (int t=tim;t;t--){
for (int i=0;i<g[t].size();i++)
st[++tn]=g[t][i];
for (int i=1;i<=tn;i++){
int k=st[i];
q.push(mp(!c[k] ? a[k]+s[k] : a[k],k));
}tn=0;
int cnt=lim;
while(cnt){
if (q.empty())break;
int i=q.top().sec;
if (!c[i]){
c[i]=1;cnt--;
q.pop();
if (tot[i]-d[i]*(t-1)>1)q.push(mp(a[i],i));
else if (d[i])st[++tn]=i;
}else {
int cnt2=min(tot[i]-d[i]*(t-1)-c[i],cnt);
c[i]+=cnt2;cnt-=cnt2;
if (tot[i]-d[i]*(t-1)==c[i])
{q.pop();if (d[i])st[++tn]=i;}
}
}
}
tn=0;
for (int i=1;i<=n;i++){
if (c[i])ans[++tn]=a[i]+s[i];
for (int j=2;j<=c[i];j++)ans[++tn]=a[i];
}sort(ans+1,ans+tn+1);reverse(ans+1,ans+tn+1);
for (int i=1;i<=tn;i++)ans[i]+=ans[i-1];
for (int i=1;i<=m;i++)
printf("%lld\n",ans[min(tn,t[i]*lim)]);
return 0;
}