BZOJ3502PA2012Tanie linie&BZOJ2288[POJ Challenge]生日礼物——模拟费用流+链表+堆
题目描述
n个数字,求不相交的总和最大的最多k个连续子序列。
1<= k<= N<= 1000000。
输入
输出
样例输入
5 2
7 -3 4 -9 5
7 -3 4 -9 5
样例输出
13
根据贪心的思想可以知道对于一段连续的正数或负数一定是一起选或者一起不选,那么我们可以将原序列连续的正数或负数缩成一个数,并将中间的$0$及两端的负数去掉,这样序列就变成了正负正负……负正的形式。先贪心地将所有正数选取,如果正数个数$\le k$直接输出正数和就是最优方案,否则我们需要去掉一些正数或选取一些两个正数之间的负数。可以发现无论是去掉正数还是选取负数,答案减少的量都是这个数的绝对值。而根据贪心的原则,如果去掉一个正数一定不会再选取相邻的两个负数,同样选取一个负数一定不会去掉相邻的两个正数。假设序列有$m$个正数,那么问题就变成了给出一个序列(每个数权值为之前的正负序列对应数的绝对值),选出$m-k$个数且不能选取相邻的数使选取数的总和最小,做法和BZOJ1150相同:维护所有数的小根堆,每次取出堆顶$b$计入答案并将与堆顶相邻的两个数$a,c$删除,将权值为$a+c-b$的一个新数插入到原来堆顶的位置。最后用双向链表维护相邻关系。
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define ll long long #define pr pair<ll,int> using namespace std; ll s[1000010]; int pre[1000010]; int suf[1000010]; int vis[1000010]; int tot; int n,k; ll ans,x; priority_queue< pr,vector<pr>,greater<pr> >q; int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) { scanf("%lld",&x); if(x>0) { ans+=x; if(s[tot]>0) { s[tot]+=x; } else { s[++tot]=x; } } if(x<0) { if(s[tot]<=0) { s[tot]+=x; } else { s[++tot]=x; } } } if(s[tot]<=0) { tot--; } n=tot; if((n+1)/2<=k) { printf("%lld",ans); return 0; } k=(n+1)/2-k; for(int i=1;i<=n;i++) { pre[i]=i-1; suf[i]=i+1; s[i]=abs(s[i]); q.push(make_pair(s[i],i)); } suf[n]=0; while(k--) { while(vis[q.top().second])q.pop(); ans-=q.top().first; int now=q.top().second; q.pop(); vis[pre[now]]=vis[suf[now]]=1; if(pre[now]&&suf[now]) { s[now]=s[pre[now]]+s[suf[now]]-s[now]; q.push(make_pair(s[now],now)); pre[now]=pre[pre[now]]; suf[now]=suf[suf[now]]; if(pre[now]) { suf[pre[now]]=now; } if(suf[now]) { pre[suf[now]]=now; } } else { vis[now]=1; pre[now]=pre[pre[now]]; suf[now]=suf[suf[now]]; if(pre[now]) { suf[pre[now]]=suf[now]; } if(suf[now]) { pre[suf[now]]=pre[now]; } } } printf("%lld",ans); }