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
",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;
}