BZOJ3502PA2012Tanie linie&BZOJ2288[POJ Challenge]生日禮物——模擬費用流+鏈表+堆
阿新 • • 發佈:2019-02-25
style zoj 雙向 true bzoj .cn cpp 之間 輸出
7 -3 4 -9 5
相同:維護所有數的小根堆,每次取出堆頂$b$計入答案並將與堆頂相鄰的兩個數$a,c$刪除,將權值為$a+c-b$的一個新數插入到原來堆頂的位置。最後用雙向鏈表維護相鄰關系。
題目描述
n個數字,求不相交的總和最大的最多k個連續子序列。
1<= k<= N<= 1000000。
輸入
輸出
樣例輸入
5 27 -3 4 -9 5
樣例輸出
13 根據貪心的思想可以知道對於一段連續的正數或負數一定是一起選或者一起不選,那麽我們可以將原序列連續的正數或負數縮成一個數,並將中間的$0$及兩端的負數去掉,這樣序列就變成了正負正負……負正的形式。先貪心地將所有正數選取,如果正數個數$\le k$直接輸出正數和就是最優方案,否則我們需要去掉一些正數或選取一些兩個正數之間的負數。可以發現無論是去掉正數還是選取負數,答案減少的量都是這個數的絕對值。而根據貪心的原則,如果去掉一個正數一定不會再選取相鄰的兩個負數,同樣選取一個負數一定不會去掉相鄰的兩個正數。假設序列有$m$個正數,那麽問題就變成了給出一個序列(每個數權值為之前的正負序列對應數的絕對值),選出$m-k$個數且不能選取相鄰的數使選取數的總和最小,做法和BZOJ1150#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); }
BZOJ3502PA2012Tanie linie&BZOJ2288[POJ Challenge]生日禮物——模擬費用流+鏈表+堆