种树 (堆模拟网络流)
种树
题目描述
cyrcyr今天在种树,他在一条直线上挖了n个坑。这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。而且由于cyrcyr的树种不够,他至多会种k棵树。假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。
输入输出格式
输入格式:
第一行,两个正整数n,k。
第二行,n个正整数,第i个数表示在直线上从左往右数第i个坑种树的获利。
输出格式:
输出1个数,表示cyrcyr种树的最大获利。
输入输出样例
输入样例#1: 复制
6 3
100 1 -1 100 1 -1
输出样例#1: 复制
200
说明
对于20%的数据,n<=20。
对于50%的数据,n<=6000。
对于100%的数据,n<=500000,k<=n/2,在一个地方种树获利的绝对值在1000000以内。
题解
一道非常好的堆模拟网络流题。
首先我们来想想\(O(n^2)\)
很显然的两重循环dp
第一重枚举第i个位置,第二重枚举选了j颗树。
对于O(n)的模拟。
我们首先明确选了 i 就不能选 i-1 和 i+1。
所以所有的情况都是由选 i 和选 i-1 ,i+1 产生的。
选 i 时, i 的贡献应当是大于 i-1 和 i+1 的。
但是当我们发现选 i-1 和 i+1 要更好时呢?
网络流有一个操作就是反悔。
我们是不是也可以反悔一下呢?
因为先选了 i ,那么下一次选择的时候,如果反悔就是选两边
就是选了周围两个点,不反悔就是选其他点
那么把周围两个点维护为一个点就可以了。
即 \(vi[i]=vi[l[i]]+vi[r[i]]-vi[i]\)
然后维护一下 \(l\) 和 \(r\) 数组。
代码
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
priority_queue<pair<int,int> >q;
int vis[1000001],ch[1000001],n,m;
long long ans;
struct node{
int vi,id,l,r;
}t[1000001];
int read(){
int x=0,w=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*w;
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++){
ch[i]=t[i].vi=read();t[i].id=i;
t[i].l=i-1;t[i].r=i+1;
q.push(make_pair(ch[i],i));
}
t[0].r=1;t[n+1].l=n;
while(m&&!q.empty()){
while(vis[q.top().second])q.pop();
int x=q.top().second;q.pop();
if(t[x].vi<0)break;
ans+=t[x].vi;
t[x].vi=t[t[x].l].vi+t[t[x].r].vi-t[x].vi;
vis[t[x].l]=vis[t[x].r]=1;
t[x].l=t[t[x].l].l;t[t[x].l].r=x;
t[x].r=t[t[x].r].r;t[t[x].r].l=x;
q.push(make_pair(t[x].vi,x));
m--;
}
printf("%lld",ans);
return 0;
}