[小白逛公園]|[SP1716]|[UVA1400]解題報告(三合一)
其實小白逛公園和SP1716是一道題,UVA1400是升級版....都是線段樹
題目連結:
1.小白逛公園(這有題面)
2.SP1716 GSS3 - Can you answer these queries III(裸題意在這)
3.UVA1400 "Ray, Pass me the dishes!"
題意:
n 個數,q 次操作
操作0 x y
把A_x 修改為y
操作1 l r
詢問區間[l, r]的最大子段和
常識資料範圍。
思路:這題難在合併,單點修改很容易。我們可以分類討論,一個區間的最大子段和會從3部分更新過來:
1.左子樹的最大子段和。2.右子樹的最大子段和。3.跨越左右子樹的最大子段和。
前兩種情況很容易,直接更新即可,考慮第三種情況怎麼更新。
我們開一個結構體
struct Tree{ ll pre,suf,sub,val; }tree[N];
pre為當前區間的最大字首和,suf為當前區間的最大字尾和,sub為當前區間的最大子段和,val為當前區間的和。
可以發現,第三種情況為 左子樹的最大字尾和+右子樹的最大字首和。
合併操作分析完畢,接下來只要考慮怎麼讓程式碼優雅了.
合併操作程式碼:
inline Tree push_up(R Tree q,R Tree e){//左子樹 右子樹 Tree w; w.pre=max(q.pre,q.val+e.pre); w.suf=max(e.suf,e.val+q.suf); w.sub=max(max(q.sub,e.sub),q.suf+e.pre); w.val=q.val+e.val; return w; }
還要注意一點,詢問的時候,若左右子樹只有一個訪問到了,一定不能將兩個直接合並,因為另一個沒有訪問的區間不屬於這次詢問的範圍。
詢問操作程式碼:
inline Tree query(R int p,R int l,R int r,R int x,R int y){ R Tree w,q,e; Rint pd1=0,pd2=0; if(l>=x&&r<=y)return tree[p]; R int mid=(l+r)>>1; if(x<=mid){ q=query(p<<1,l,mid,x,y); pd1=1; } if(y>mid){ e=query(p<<1|1,mid+1,r,x,y); pd2=1; } if(pd1&&pd2)w=push_up(q,e); else if(pd1)w=q; else if(pd2)w=e; return w; }
完整程式碼:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define R register #define ll long long int using namespace std; const int N=500005<<2; ll n,q,a[N]; struct Tree{ ll pre,suf,sub,val; }tree[N]; inline Tree push_up(R Tree q,R Tree e){ Tree w; w.pre=max(q.pre,q.val+e.pre); w.suf=max(e.suf,e.val+q.suf); w.sub=max(max(q.sub,e.sub),q.suf+e.pre); w.val=q.val+e.val; return w; } inline void build(R int p,R int l,R int r){ if(l==r){ tree[p].pre=tree[p].suf=tree[p].sub=tree[p].val=a[l]; return; } R int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); tree[p]=push_up(tree[p<<1],tree[p<<1|1]); } inline void update(R int p,R int l,R int r,R int x,R ll k){//A[x]=k if(l==r){ tree[p].pre=tree[p].suf=tree[p].sub=tree[p].val=k; return; } R int mid=(l+r)>>1; if(x<=mid) update(p<<1,l,mid,x,k); else update(p<<1|1,mid+1,r,x,k); tree[p]=push_up(tree[p<<1],tree[p<<1|1]); } inline Tree query(R int p,R int l,R int r,R int x,R int y){ R Tree w,q,e; R int pd1=0,pd2=0; if(l>=x&&r<=y)return tree[p]; R int mid=(l+r)>>1; if(x<=mid){ q=query(p<<1,l,mid,x,y); pd1=1; } if(y>mid){ e=query(p<<1|1,mid+1,r,x,y); pd2=1; } if(pd1&&pd2)w=push_up(q,e); else if(pd1)w=q; else if(pd2)w=e; return w; } int main(){ scanf("%lld",&n); for(R int i=1;i<=n;i++) scanf("%lld",&a[i]); build(1,1,n); scanf("%lld",&q); for(R int i=1;i<=q;i++){ R int pd,x,y; scanf("%d%d%d",&pd,&x,&y); if(pd==0){ update(1,1,n,x,y); } else{ Tree w; if(x>y)swap(x,y); w=query(1,1,n,x,y); printf("%lld\n",w.sub); } } return 0; }View Code
其實這道題的合併還好,只需要取幾個max,Uva1400 的合併才叫噁心,
題意:
給出一個長度為n的整數序列D,你的任務是對m個詢問做出回答。
對於詢問(a,b),需要找到兩個下標x和y,
使得a<=x<=y<=b,並且Dx+Dx+1+....+Dy儘量大。
如果有多組滿足條件的x和y,x儘量小。
如果還有多個解,y應該儘量小。
這道題的結構體需要記錄的東西更多:
struct Tree{ LL pre,suf,sub,val,lch,rch,ll,rr;
// 字首 字尾 最大欄位和 區間和 欄位和左端點和右端點 字首和右端點 字尾和左端點 }tree[N<<2];
為什麼要多記錄這4個東西?它要求的不是最大子段和,而是最大子段和的左右端點,我們考慮合併的時候需要用到最大子段和,字首和,字尾和,所以我們需要記錄這些。
合併的時候需要分類討論:
1.在保證最大子段和的前提下,x儘量小的情況下,y也儘量小。
上一道題怎麼合併的?取了3個max,總共有7種情況,分類討論就好了,
麻煩...
合併程式碼:
inline Tree update(Tree q,Tree e,int pos){ Tree a; a.pre=a.suf=a.sub=a.val=0;a.val=q.val+e.val;//字首 if((q.val+e.pre)<=q.pre){ a.pre=q.pre; a.ll=q.ll; } else//字首 { a.pre=q.val+e.pre; a.ll=e.ll; } if((e.val+q.suf)>=e.suf){//字尾 a.suf=e.val+q.suf; a.rr=q.rr; } else//字尾 { a.suf=e.suf; a.rr=e.rr; } if((q.sub>=e.sub)&&(q.sub>=q.suf+e.pre)){//欄位和 a.sub=q.sub; a.lch=q.lch; a.rch=q.rch; } else if((q.suf+e.pre>=q.sub)&&(q.suf+e.pre>=e.sub))//欄位和 { a.sub=q.suf+e.pre; a.lch=q.rr; a.rch=e.ll; } else//欄位和 { a.sub=e.sub; a.lch=e.lch; a.rch=e.rch; } return a; }View Code
完整程式碼:
//a.pre=max(q.pre,q.val+e.pre); //a.suf=max(e.suf,e.val+q.suf);原始合併方程 //a.sub=max(max(q.sub,e.sub),q.suf+e.pre); #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define R register #define LL long long int using namespace std; const int N = 5e5+5 ; LL n,m,d[N],num; struct Tree{ LL pre,suf,sub,val,lch,rch,ll,rr;//字首 字尾 最大欄位和 區間和 左區間 右區間 }tree[N<<2]; inline void init(){ memset(d,0,sizeof(d)); memset(tree,0,sizeof(tree)); } inline Tree update(Tree q,Tree e,int pos){ Tree a; a.pre=a.suf=a.sub=a.val=0;a.val=q.val+e.val;//字首 if((q.val+e.pre)<=q.pre){ a.pre=q.pre; a.ll=q.ll; } else//字首 { a.pre=q.val+e.pre; a.ll=e.ll; } if((e.val+q.suf)>=e.suf){//字尾 a.suf=e.val+q.suf; a.rr=q.rr; } else//字尾 { a.suf=e.suf; a.rr=e.rr; } if((q.sub>=e.sub)&&(q.sub>=q.suf+e.pre)){//欄位和 a.sub=q.sub; a.lch=q.lch; a.rch=q.rch; } else if((q.suf+e.pre>=q.sub)&&(q.suf+e.pre>=e.sub))//欄位和 { a.sub=q.suf+e.pre; a.lch=q.rr; a.rch=e.ll; } else//欄位和 { a.sub=e.sub; a.lch=e.lch; a.rch=e.rch; } return a; } inline void build(R int p,R int l,R int r){ if(l==r){ tree[p].pre=tree[p].suf=tree[p].sub=tree[p].val=d[l]; tree[p].ll=tree[p].rr=tree[p].lch=tree[p].rch=l; return; } R int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); tree[p]=update(tree[p<<1],tree[p<<1|1],p); } inline Tree query(R int p,R int l,R int r,R int x,R int y){ R Tree q,e,w; R int pd1=0,pd2=0; if(l>=x&&r<=y) { return tree[p]; } R int mid=(l+r)>>1; if(x<=mid){ q=query(p<<1,l,mid,x,y); pd1=1; } if(y>mid){ e=query(p<<1|1,mid+1,r,x,y); pd2=1; } if(pd1&&pd2)w=update(q,e,p); else if(pd1)w=q; else if(pd2)w=e; return w; } int main(){ while(~scanf("%lld%lld",&n,&m)){ init(); num++; printf("Case %lld:\n",num); for(R int i=1;i<=n;i++) scanf("%lld",&d[i]); build(1,1,n); for(R int i=1;i<=m;i++){ R int a,b; R Tree c; scanf("%d%d",&a,&b); c=query(1,1,n,a,b); printf("%lld %lld\n",c.lch,c.rch); } } }View Code
如果你還是無法調對程式,我這有對拍用的隨機數程式碼:
#include<cstdio> #include<cstdlib> #include<ctime> #include<algorithm> using namespace std; int main() { freopen("test.in","w",stdout); srand((unsigned)time(NULL)); int T = rand()%5 + 1; while(T--) { int n = rand()%10+1,m = rand()%5+1; printf("%d %d\n",n,m); for(int i = 1; i <= n; i++) if(rand()%2){ printf("%d ",rand()%100); }else printf("%d ",-rand()%100); printf("\n"); for(int i = 1; i <= m; i++) { int l = rand()%n+1,r = rand()%n+1; if(l>r) swap(l,r); printf("%d %d\n",l,r); } } return 0; }View Code
鬼知道這道題我調了多久...
總結:第一次做到合併的複雜操作,估計其它型別的合併也是類似的方法,至少我會的合併方法不是簡單的tree[p]=tree[p<<1]+tree[p<<1|1]了。