1. 程式人生 > >CF280D k-Maximum Subsequence Sum 線段樹

CF280D k-Maximum Subsequence Sum 線段樹

題目大意:
給你一個長度為n(1<=n<=105)的序列,支援兩種操作:
1.修改某個位置的值。
2.在區間[l,r]裡選擇不超過k(1<=k<=20)個不相交的子區間和的最大值。
運算元m<=105

分析:
因為k比較小,可以考慮使用費用流的模型。源點S向每個點i連一條流量為1,費用為0的邊;ii+1連一條費用為ai,流量為1的邊;i向匯點T連流量為1,費用為0的邊。那麼,一滴從S>i>j>

T的流,代表選取區間[i,j1]。可以發現的是,每多流一滴流量,就多一個區間。問題就變成了使用不多於k滴流量,能得到的最大費用。因為每次增廣只會多一點流量,而每次要查詢的最長路就是每次增廣的貢獻,這個貢獻是由一段區間構成。

所以,每次增廣相當於找到詢問區間的最大子區間和,然後再把該區間取反(反向弧的費用是正向弧的相反數)。舉個例子,如果第一次選了區間[1,4],然後取反後取了區間[3,6],此時相當於取了區間[1,2][5,6]增廣完k次,或者增廣貢獻小於0時,就得到了答案。

因為不可能同時選共起點或共終點的區間,假如我們第一次取了

[1,4],那麼一定不可能取反第二次後會取[1,5],這樣得到的區間是[4,5],因為我們保證每次增廣的貢獻大於等於0,這個區間是比[1,4]大的,與一開始就取最大子區間矛盾。所以每次增廣必然會增加一個區間。

現在就可以用線段樹維護了,相當於維護一個最大子區間值,當然,同時肯定要維護最大左區間和與最大右區間和區間和,單點修改就不用lazy了,不然真的太毒瘤了。當然,由於有反轉操作,還需要維護最小的子區間和,反轉時swap一下就好,打上反轉標記。
定義一個區間型別及使用過載運算子來處理區間加可以減少程式碼量。

程式碼:

#include <iostream>
#include <cstdio> #include <cmath> #include <queue> const int maxn=1e5+7; using namespace std; int n,m,x,op,y,k; struct rec{ int l,r,s; }; rec operator + (rec x,rec y) { return (rec){x.l,y.r,x.s+y.s}; } bool operator < (rec x,rec y) { return x.s<y.s; } struct node{ rec smax,smin,lmax,rmax,lmin,rmin,sum; int rev; }t[maxn*4]; queue <node> q; void neww(int p,int l,int k) { t[p].smax=(rec){l,l,k}; t[p].lmax=(rec){l,l,k}; t[p].rmax=(rec){l,l,k}; t[p].smin=(rec){l,l,k}; t[p].lmin=(rec){l,l,k}; t[p].rmin=(rec){l,l,k}; t[p].sum=(rec){l,l,k}; } node merge(node x,node y) { node z; z.smax=max(x.smax,y.smax); z.smax=max(z.smax,x.rmax+y.lmax); z.smin=min(x.smin,y.smin); z.smin=min(z.smin,x.rmin+y.lmin); z.lmax=max(x.lmax,x.sum+y.lmax); z.rmax=max(y.rmax,x.rmax+y.sum); z.lmin=min(x.lmin,x.sum+y.lmin); z.rmin=min(y.rmin,x.rmin+y.sum); z.sum=x.sum+y.sum; z.rev=0; return z; } void clean(int p) { swap(t[p].smax,t[p].smin); swap(t[p].lmax,t[p].lmin); swap(t[p].rmax,t[p].rmin); t[p].smax.s*=-1; t[p].smin.s*=-1; t[p].lmax.s*=-1; t[p].lmin.s*=-1; t[p].rmax.s*=-1; t[p].rmin.s*=-1; t[p].sum.s*=-1; t[p].rev^=1; } void change(int p,int l,int r,int x,int k) { if (l==r) { neww(p,l,k); return; } int mid=(l+r)/2; if (t[p].rev) { clean(p*2); clean(p*2+1); t[p].rev^=1; } if (x<=mid) change(p*2,l,mid,x,k); else change(p*2+1,mid+1,r,x,k); t[p]=merge(t[p*2],t[p*2+1]); } void rev(int p,int l,int r,int x,int y) { if ((l==x) && (r==y)) { clean(p); return; } int mid=(l+r)/2; if (t[p].rev) { clean(p*2); clean(p*2+1); t[p].rev^=1; } if (y<=mid) rev(p*2,l,mid,x,y); else if (x>mid) rev(p*2+1,mid+1,r,x,y); else { rev(p*2,l,mid,x,mid); rev(p*2+1,mid+1,r,mid+1,y); } t[p]=merge(t[p*2],t[p*2+1]); } node query(int p,int l,int r,int x,int y) { if ((l==x) && (r==y)) return t[p]; int mid=(l+r)/2; if (t[p].rev) { clean(p*2); clean(p*2+1); t[p].rev^=1; } if (y<=mid) return query(p*2,l,mid,x,y); else if (x>mid) return query(p*2+1,mid+1,r,x,y); else { return merge(query(p*2,l,mid,x,mid),query(p*2+1,mid+1,r,mid+1,y)); } } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%d",&x); change(1,1,n,i,x); } scanf("%d",&m); for (int i=1;i<=m;i++) { scanf("%d",&op); if (op==0) { scanf("%d%d",&x,&k); change(1,1,n,x,k); } else { scanf("%d%d%d",&x,&y,&k); int ans=0; while (k--) { node d=query(1,1,n,x,y); if (d.smax.s<0) break; ans+=d.smax.s; rev(1,1,n,d.smax.l,d.smax.r); q.push(d); } printf("%d\n",ans); while (!q.empty()) { node d=q.front(); q.pop(); rev(1,1,n,d.smax.l,d.smax.r); } } } }