[NOI2017]蔬菜 贪心
题解:
首先每天蔬菜会变质这点并不好处理,我们考虑让时间倒流,从后向前处理,这样的话就相当于每天都会得到一定量的蔬菜。
这样做有什么好处呢?
我们可以发现一个性质:如果从后向前贪心卖菜,那么因为现在可以卖的菜,以后一定还可以卖(因为变成了得到菜),因此贪心就是对的了。
因此我们用堆维护一下,从后向前贪心的卖菜,每次优先卖价格高的,第一次卖的菜价格要加上奖励的贡献,并且只能先卖一个,因为卖完这一个之后的同种菜没有奖励了,相当于贡献有变化。
这样向前一直贪到第一天,于是我们就得到了卖1 ~ 100000天的最高收入。
那么已知1到100000的最高收入,如何利用之前的决策信息快速的得知1到99999的最高收入呢?
我们发现,这2者之间唯一的差别就是少买了最多m个蔬菜。
那么我们已知我们已经卖了have个蔬菜,已知1到now这么多天最多卖$m * now$个蔬菜,那么如果$have > m * now$,我们就要舍弃部分蔬菜使得$have <= m * now$成立。
用堆维护,每次撤销卖价最低的蔬菜即可,注意如果撤销的这个蔬菜是剩下的最后一个了,那么代价要加上奖励的代价。
为什么这样是对的呢?
因为如果这个菜是在后面被卖出的话,前面肯定也可以卖,所以如果我们撤销了某天卖出的蔬菜,可以看做在这天后面卖出的蔬菜被挤上来顶替了这个被撤销的蔬菜,于是代价就会始终等价于撤销了最后一天卖出的蔬菜。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 100100 5 #define LL long long 6 7 int n, m, k, can;//存下浪费了多少的卖菜名额 8 const int maxn = 100000; 9 LL ans[AC], last[AC], sum[AC], d[AC], have[AC], v[AC], s[AC], c[AC]; 10 int sta[AC], top; 11 //每一天的答案,每种菜最后一天出现是在哪一天,那天有多少这种菜,d表示以后每天增加多少 12 //have表示这种菜现在卖出了多少,a表示卖菜的基础收益,s表示额外收益,c表示初始库存 13 int Head[AC], Next[AC], date[AC], tot; 14 struct node{ 15 LL v; 16 int id; 17 }; 18 19 struct cmp2{//小根堆 20 bool operator () (node a, node b){ return a.v > b.v;} 21 }; 22 23 struct cmp{//大根堆 24 bool operator () (node a, node b){return a.v < b.v;} 25 }; 26 27 priority_queue<node, vector<node>, cmp> q; 28 priority_queue<node, vector<node>, cmp2> q2; 29 30 inline int read(){ 31 int x = 0;char c = getchar(); 32 while(c > '9' || c < '0') c = getchar(); 33 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 34 return x; 35 } 36 37 inline void add(int f, int w){ 38 date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot; 39 } 40 41 void pre() 42 { 43 n = read(), m = read(), k = read(); 44 for(R i = 1; i <= n; i ++) 45 { 46 v[i] = read(), s[i] = read(), c[i] = read(), d[i] = read(); 47 if(d[i]) 48 { 49 last[i] = c[i] / d[i] + 1, sum[i] = c[i] - (c[i] / d[i]) * d[i]; 50 if(!sum[i]) sum[i] = d[i], last[i] --; 51 add(last[i], i);//把这个菜挂在第一次出现的地方 52 } 53 else last[i] = maxn, sum[i] = c[i], add(maxn, i); 54 } 55 } 56 57 void get()//从后向前贪心,小根堆维护撤销 58 { 59 for(R i = 1; i <= n; i ++)//把卖出去的菜压入栈中 60 if(have[i] == 1) q2.push((node){v[i] + s[i], i}); 61 else if(have[i]) q2.push((node){v[i], i}); 62 for(R i = maxn - 1; i; i --)//向前撤销 63 { 64 ans[i] = ans[i + 1];//初始状态 65 LL y = m; 66 if(i * m >= can) continue; 67 else y = can - i * m, can -= y; 68 while(y && !q2.empty()) 69 { 70 node x = q2.top(); 71 if(have[x.id] == 1) ans[i] -= x.v, q2.pop(), -- y;//如果只剩一个了就要弹出去了 72 else 73 { 74 if(have[x.id] > y)//如果反正取不完的话就随便取,否则就要注意不能取完了 75 { 76 have[x.id] -= y, ans[i] -= x.v * y, y = 0; 77 if(have[x.id] == 1) 78 q2.pop(), x.v += s[x.id], q2.push(x); 79 } 80 else 81 { 82 ans[i] -= x.v * (have[x.id] - 1), y -= (have[x.id] - 1); 83 have[x.id] = 1, q2.pop(), x.v += s[x.id], q2.push(x); 84 } 85 } 86 } 87 } 88 } 89 90 void build()//从后向前贪心,大根堆维护取值 91 { 92 for(R i = maxn; i; i --) 93 { 94 for(R j = Head[i]; j; j = Next[j]) 95 { 96 int x = date[j]; 97 q.push((node){v[x] + s[x], x}); 98 } 99 while(top) q.push((node){v[sta[top]], sta[top]}), -- top;//放回去 100 LL y = m; 101 while(y && !q.empty()) 102 { 103 node x = q.top(); 104 if(!have[x.id]) 105 { 106 q.pop(), ans[maxn] += x.v, x.v -= s[x.id]; 107 have[x.id] = 1, -- y, q.push(x); 108 } 109 else 110 { 111 LL tmp = (last[x.id] - i) * d[x.id] + sum[x.id] - have[x.id];//获取这个菜还剩多少 112 if(tmp >= y) have[x.id] += y, ans[maxn] += x.v * y, y = 0;//还有就不用pop了 113 else 114 { 115 y -= tmp, have[x.id] += tmp, q.pop(); 116 ans[maxn] += x.v * tmp; 117 if(d[x.id]) sta[++top] = x.id; 118 } 119 } 120 } 121 can += m - y; 122 } 123 } 124 125 void work() 126 { 127 for(R i = 1; i <= k; i ++) 128 printf("%lld\n", ans[read()]); 129 } 130 131 int main() 132 { 133 // freopen("in.in", "r", stdin); 134 pre(); 135 build();//先获取最后一个的值 136 get();//再倒退出整个数组 137 work(); 138 // fclose(stdin); 139 return 0; 140 }