【BZOJ3502/2288】PA2012 Tanie linie/【POJ Challenge】生日礼物 堆+链表(模拟费用流)
【BZOJ3502】PA2012 Tanie linie
Description
n个数字,求不相交的总和最大的最多k个连续子序列。
1<= k<= N<= 1000000。
Sample Input
5 2
7 -3 4 -9 5
7 -3 4 -9 5
Sample Output
13
题解:跟1150和2151差不多。
我们先做一些预处理,因为连续的正数和连续的负数一定是要么都选要么都不选,所以可以将它们合并成一个数,同时区间中的零以及左右两端的负数没有意义,可以将它们删掉。然后我们得到的序列就变成:正-负-正-...-负-正。
然后我们贪心的把所有正数都选了,如果正数的部分<=k,那么直接取光即可,否则,我们还要将我们的子串个数减少一些。如何减少呢?1:将某个正数由选变为不选。2.将某个负数由不选变为选。我们发现,这两种情况都会使答案减少:那个数的绝对值,所以我们可以用堆维护所有数的绝对值,然后每次都贪心的选取绝对值小的。
但是直接贪心肯定不行,我们要模拟费用流的过程,也就是加入一个反悔操作。发现:如果对一个整数进行了1操作,那么我们就无法对它相邻的负数进行2操作;如果对一个负数进行了2操作,那么我们就无法对它相邻的正数进行1操作。所以我们再堆中删掉与它相邻的点。那么怎么反悔呢?如果当前是b,b的前驱是a,后继是c,那么向堆中加入a+c-b。如果再次选择了a+c-b,意味着我们撤销了对b的操作,而是对a和c进行操作,这样就和费用流是一样的了。
#include <cstdio> #include <cstring> #include <iostream> #include <queue> #include <utility> #include <algorithm> #define mp(A,B) make_pair(A,B) using namespace std; typedef long long ll; const int maxn=1000010; int n,m,k; ll ans; ll v[maxn],p[maxn]; int pre[maxn],nxt[maxn],del[maxn]; typedef pair<ll,int> pli; priority_queue<pli> q; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-')f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar(); return ret*f; } int main() { n=rd(),m=rd(); int i,a,b,tp=0; for(i=1;i<=n;i++) { v[i]=rd(); if(v[i]>0) { ans+=v[i]; if(p[tp]>0) p[tp]+=v[i]; else p[++tp]=v[i]; } if(v[i]<0) { if(p[tp]<=0) p[tp]+=v[i]; else p[++tp]=v[i]; } } if(p[tp]<=0) tp--; n=tp; if((n+1)>>1<=m) { printf("%lld\n",ans); return 0; } m=((n+1)>>1)-m; for(i=1;i<=n;i++) p[i]=abs(p[i]),q.push(mp(-p[i],i)),pre[i]=i-1,nxt[i]=(i<n)?(i+1):0; while(m--) { while(del[q.top().second]) q.pop(); ans+=q.top().first,i=q.top().second,q.pop(); a=pre[i],b=nxt[i],del[a]=del[b]=1; if(a&&b) { pre[i]=pre[a],nxt[i]=nxt[b]; if(pre[i]) nxt[pre[i]]=i; if(nxt[i]) pre[nxt[i]]=i; p[i]=p[a]+p[b]-p[i],q.push(mp(-p[i],i)); } else { pre[i]=pre[a],nxt[i]=nxt[b]; if(pre[i]) nxt[pre[i]]=nxt[i]; if(nxt[i]) pre[nxt[i]]=pre[i]; del[i]=1; } } printf("%lld",ans); return 0; }
| 欢迎来原网站坐坐! >原文链接<