一类贪心题乱编
PS:明天就要考数学了,好慌
我们考虑一类贪心题,它们常常是某些物品带有一定价值,若干天,每次能选一定量,但是随着一段时间可决策集合越来越窄.
对于这类题,一个比较通用的想法就是倒过来考虑,因为如果倒过来考虑,就变成决策集合单调不减,于是往往可以直接用堆之类的数据结构进行贪心.
类似一种反悔贪心?常常体现为贪心实质是在模拟费用流(然而我基本不会网络流)
例题1,[NOI2017]蔬菜
题意:太长了就不说了,自己看吧,大致就是上面的模型
发现正着做十分难做,考虑倒着贪心.
因为第p-1天答案显然是第p天减掉最劣的决策(因为p-1对于p来说决策集合单调不减,所以p能选的所有p-1都能选,所以可以随便删)
于是我们只要求出第P天的值,(P = max($p_{j}$)),然后就可以倒推1~P-1
考虑倒着推怎么做.我们发现题目一堆奇奇怪怪的限制在倒推中突然就有了意义,如果某堆食物从某天开始就不变质了,就把它加进去(又是因为决策集合单调不减,对于倒着推来说,如果当前不变质,之后就不会再变质了)
于是可以直接塞堆里,贪心
/*[NOI2017]蔬菜*/ #include<bits/stdc++.h> using namespace std; #define ll long long int read(){ char c = getchar(); int x = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar(); return x; } struct Node{ ll v,id; Node (ll fi = 0,ll se = 0){ v = fi,id = se; } bool operator <(const Node &a)const{ return v < a.v; } }; const int N = 1e5 + 10; priority_queue<Node>q; int a[N],s[N],c[N],x[N],n,m,k; int P = 1e5; vector<int>d[N];/*第i天会有哪些元素加进来*/ int sel[N]; bool vis[N]; Node st[N];int top; ll ans[N];ll ss; #define pb push_back int main(){ n = read(),m = read(),k = read(); for(int i = 1; i <= n; ++i) a[i] = read(),s[i] = read(),c[i] = read(),x[i] = read(); for(int i = 1; i <= n; ++i){ if(x[i] == 0) d[P].pb(i); else d[min(P,(c[i] - 1 + x[i]) / x[i])].pb(i); } for(int i = P; i; --i){ for(int j = 0; j < (int)d[i].size(); ++j){ int x = d[i][j]; q.push(Node(a[x] + s[x],d[i][j])); } for(int j = m; j && !q.empty();){ Node now = q.top();q.pop(); if(!vis[now.id]){ sel[now.id]++; ans[P] += now.v; if(c[now.id] > 1) q.push(Node(a[now.id],now.id)); vis[now.id] = 1; --j; } else{ int res = min(1ll * j,c[now.id] - 1ll * (i - 1) * x[now.id] - sel[now.id]); j -= res;ans[P] += 1ll * res * now.v; sel[now.id] += res;if(sel[now.id] != c[now.id]) st[++top] = now; } } while(top) q.push(st[top]),top--; } while(q.size()) q.pop(); memset(vis,0,sizeof(vis)); for(int i = 1; i <= n; ++i){ ss += sel[i]; if(sel[i] == 0) continue; if(sel[i] == 1) q.push(Node(-a[i]-s[i],i)); else q.push(Node(-a[i],i)); } for(int i = P - 1; i; --i){ ans[i] = ans[i+1]; while(ss > i * m){ ss--; Node now = q.top();q.pop(); ans[i] -= -now.v;sel[now.id]--; if(sel[now.id] > 1) q.push(now); if(sel[now.id] == 1) q.push(Node(-a[now.id] - s[now.id],now.id)); } } // return 0; while(k--){ int p = read(); printf("%lld\n",ans[p]); } return 0; }
upd:还有从费用流角度的理解,先咕着
例题2.[CF505E]
首先肯定是要二分的,设当前二分的值为H,所以就变成了最大值能否不超过H
有一个极度鬼畜的max($h_{i}$ - p,0)
我们依然考虑倒着做,考虑设所有值初始位置都是H,每时刻-=a[i],任意时刻都不能<0,你可以给它加上p,是否存在一种方案,使得最终位置 >= h[i]
只要按变成<0的顺序,优先+那些会先<0的,贪心地+p最后再检查是否全部合法就可以了.
/*CF505E Mr. Kitayuta vs. Bamboos*/ #include<bits/stdc++.h> using namespace std; #define ll long long int read(){ char c = getchar(); int x = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar(); return x; } const int N = 1e5 + 10; ll a[N],h[N],n,m,k,p; struct NUM{ int ed;ll v;int id,st;/*几天后会小于0*/ bool operator <(const NUM &a)const{ return ed > a.ed; } NUM (int _a = 0,ll _b = 0,int _c = 0,int _d = 0){ ed = _a,v = _b,id = _c,st = _d; } }; priority_queue<NUM>q; bool check(ll x){ while(q.size()) q.pop(); for(int i = 1; i <= n; ++i){ if(x - m * a[i] >= h[i]) continue; q.push(NUM(x / a[i],x,i,0)); // cout<<i<<' '<<x / a[i]<<endl; } for(int i = 1; i <= m; ++i){ ll res = m - i; for(int j = 1; j <= k; ++j){ if(q.empty()) return 1; NUM now = q.top();q.pop(); if(now.ed < i) return 0; ll v = now.v - 1ll * (i - now.st) * a[now.id] + p; if(v - res * a[now.id] >= h[now.id]) continue; q.push(NUM(max((ll)i,i + v / a[now.id]),v,now.id,i)); } } if(q.empty()) return 1;return 0; } int main(){ n = read(),m = read(),k = read(),p = read(); for(int i = 1; i <= n; ++i) h[i] = read(),a[i] = read(); ll l = 1,r = 1e15; ll ans = 0; while(l <= r){ ll mid = (l + r) >> 1; if(check(mid)){ ans = mid; r = mid - 1; } else l = mid + 1; } cout<<ans<<endl; return 0; }