1. 程式人生 > >NOI2005 維護數列 lg2042

NOI2005 維護數列 lg2042

為什麽 P20 理解 沒有 define 細節 swa oal include

這道題據說是noi題目中算是比較毒瘤的數據結構題了,5k多的代碼加上隨手寫掛細節,我調了兩天

題面見https://www.luogu.org/problemnew/show/P2042

(歪個題,這類區間操作的數據結構題可以先去寫GSS系列題,會比較容易理解合並的操作)

(再歪個題,我將在近期補一篇博客說一下自己區間樹的理解)

一共維護6個操作

第一個操作為插入一棵新的子樹,因為是插子樹,所以單點插是要t飛的,那麽就考慮一個優化,先建好樹再掛到他應該出現的位置上

復雜度從nlogn降到了n。

第二個操作刪除就把l-1,r+1分別轉到根和根的左兒子,那麽根的左兒子的右兒子就是題目中需要處理的區間了,直接遞歸刪就完事了

第三個操作翻轉就是文藝平衡樹裏的那樣了

第四個區間覆蓋的思路也和操作二類似,把需要操作的區間專門轉出來,然後打個lazy_tag就好了

第五個操作也同上,把區間轉出來之後輸出一下sum

第六個操作就直接查詢根節點信息裏的區間最大值就好了

操作拆開考慮是都不難,但揉在一起的時候就麻了,希望大家能順利寫出這道題。

#include<bits/stdc++.h>
#include<queue>
using namespace std;
#define INF 1000000000
int read()
{
    int x=0,f=1;char ch=getchar();
    while
(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();} while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getchar();} return x*f; } int n,m,cnt,root,a[1000010],id[1000010]; queue<int> q; struct node{ int ch[2],sum,cnt,val,f,size,lmax,rmax,maxx; bool cov,rev; }st[1000010]; inline bool identify(int
p){ return st[st[p].f].ch[1]==p; }//認定自己是父親的左兒子還是右兒子 inline void connect(int x,int fa,int son){ st[x].f=fa;st[fa].ch[son]=x;return; }//連接操作 inline void push_up(int x){//上推操作 int ls=st[x].ch[0];int rs=st[x].ch[1]; st[x].size=st[ls].size+st[rs].size+1;//該子樹的大小為左右子樹大小之和加上一 st[x].sum=st[ls].sum+st[rs].sum+st[x].val;//子樹的權值和等於左右子樹的和加上當前點的權值 st[x].maxx=max(st[ls].rmax+st[x].val+st[rs].lmax,max(st[ls].maxx,st[rs].maxx));//最大值,左端最大值,右端最大值,記住要加上當前點的權值,其它就是常規操作 st[x].lmax=max(st[ls].lmax,st[ls].sum+st[x].val+st[rs].lmax); st[x].rmax=max(st[rs].rmax,st[rs].sum+st[x].val+st[ls].rmax); } inline void push_down(int x){//下移標記,這個操作我寫掛了兩次,頭麻 int ls=st[x].ch[0];int rs=st[x].ch[1]; if(st[x].cov){//如果有覆蓋操作,翻轉就沒用了 st[x].cov=st[x].rev=0; if(ls) st[ls].val=st[x].val,st[ls].cov=true,st[ls].sum=st[ls].size*st[ls].val;//有左兒子的話下推標記 if(rs) st[rs].val=st[x].val,st[rs].cov=true,st[rs].sum=st[rs].size*st[rs].val;//有右兒子的話下推標記 if(st[x].val>=0){ if(ls) st[ls].lmax=st[ls].rmax=st[ls].maxx=st[ls].sum; if(rs) st[rs].lmax=st[rs].rmax=st[rs].maxx=st[rs].sum; } else{ if(ls) st[ls].lmax=st[ls].rmax=0,st[ls].maxx=st[ls].val; if(rs) st[rs].lmax=st[rs].rmax=0,st[rs].maxx=st[rs].val; } } /* 下推標記其實沒有什麽亮點,就是寫起來比較長然後容易寫掛細節 這裏著重解釋一下為什麽左右max可以為0,而區間max一定要選擇一個值 因為區間合並的時候需要用左右兒子的左右端的最大值,值設為0的意思就是不選擇這個區間裏的任何東西 如果是寫過線段樹上維護區間的人可能會想既然不選的東西,值設成負的有什麽大不了呢,可這裏就有一個 不大一樣的操作,左右子樹信息上推的時候要加上當前節點的值,如果不把lmax和rmax設成0,就要分多種情況討論 這代碼本來寫起來就麻煩了,再分類討論......所以,當前節點的值若小於0的時候,lmax和rmax就設為0 */ if(st[x].rev){ st[x].rev^=1;st[ls].rev^=1;st[rs].rev^=1; swap(st[ls].lmax,st[ls].rmax);swap(st[rs].lmax,st[rs].rmax); swap(st[ls].ch[0],st[ls].ch[1]);swap(st[rs].ch[0],st[rs].ch[1]);//翻轉的時候記得換兒子 } } inline void rotate(int x){//splay的常規操作就沒啥好說的了 int y=st[x].f;int z=st[y].f; int yson=identify(x);int zson=identify(y); int b=st[x].ch[yson^1]; connect(b,y,yson);connect(y,x,(yson^1));connect(x,z,zson); push_up(y);push_up(x);return; } inline void splay(int x,int goal){ while(st[x].f!=goal){ int y=st[x].f;int z=st[y].f; int yson=identify(x);int zson=identify(y); if(z!=goal){ if(yson==zson) rotate(y); else rotate(x); } rotate(x); } if(!goal) root=x; return; } inline int find(int p,int rk){//此處運用了一個區間樹查詢的思想,我會在近期寫一份關於區間樹的博客,請期待(逃 push_down(p); int ls=st[p].ch[0];int rs=st[p].ch[1]; if(st[ls].size+1==rk) return p; if(st[ls].size>=rk) return find(ls,rk); return find(rs,rk-st[ls].size-1); } inline void recycle(int p){//這辣雞題卡內存,那麽就廢物利用唄 if(!p) return; int ls=st[p].ch[0];int rs=st[p].ch[1]; recycle(ls);recycle(rs);q.push(p); st[p].ch[0]=st[p].ch[1]=st[p].f=0; st[p].rev=st[p].cov=0;return; } inline int split(int k,int tot){//這個操作就找到自己要修改的區間端點的l-1和r+1,分別旋轉到根和根的右兒子 int x=find(root,k);int y=find(root,k+tot+1);//那麽根的右兒子的左兒子就是當前要操作的區間了 splay(x,0);splay(y,x);return st[y].ch[0];//這個和文藝平衡樹的操作類似,可以參考那份代碼進行理解 }//最後返回一下當前區間子樹的根節點 inline void query(int k,int tot){ int x=split(k,tot); printf("%d\n",st[x].sum);return; } inline void cov(int k,int tot,int val){ int x=split(k,tot);int y=st[x].f; st[x].val=val;st[x].cov=true;st[x].sum=st[x].size*val; if(val>=0){st[x].lmax=st[x].rmax=st[x].maxx=st[x].sum;} else{st[x].lmax=st[x].rmax=0;st[x].maxx=val;} push_up(y);push_up(st[y].f);return; }//區間覆蓋區間翻轉道理都差不多 //我就解釋一下區間覆蓋把,區間覆蓋的時候,當前值改一下,lazy_tag改一下,相應的最大值總和改一下,上推一下,它就很合理...... inline void rev(int k,int tot){ int x=split(k,tot); int y=st[x].f;int z=st[y].f; if(!st[x].cov){ st[x].rev^=1;swap(st[x].ch[0],st[x].ch[1]); swap(st[x].lmax,st[x].rmax); push_up(y);push_up(st[y].f); } } inline void erase(int k,int tot){ int x=split(k,tot);int y=st[x].f; recycle(x);st[y].ch[0]=0; push_up(y);push_up(st[y].f);return; }//區間刪除 inline void build(int l,int r,int f){ if(l>r) return; int mid=(l+r)>>1;int now=id[mid];int last=id[f]; if(l==r){ st[now].sum=a[l];st[now].size=1; st[now].rev=st[now].cov=0; if(a[l]>=0){st[now].lmax=st[now].rmax=st[now].maxx=a[l];} else{st[now].lmax=st[now].rmax=0;st[now].maxx=a[l];} } else build(l,mid-1,mid),build(mid+1,r,mid); st[now].val=a[mid];st[now].f=last;push_up(now); st[last].ch[mid>=f]=now;return; }//這個操作它對每個節點進行標號並依照這個進行建樹 inline void insert(int k,int tot){ for(int i=1;i<=tot;i++) a[i]=read(); for(int i=1;i<=tot;i++){ if(!q.empty()){id[i]=q.front();q.pop();} else id[i]=++cnt; } build(1,tot,0);int z=id[(1+tot)>>1]; int x=find(root,k+1);int y=find(root,k+2); splay(x,0);splay(y,x); st[z].f=y;st[y].ch[0]=z; push_up(y);push_up(x);return; }//在插入一棵新的子樹的時候,可以先把這棵子樹建出來,然後往原樹上一掛,美滋滋 int main(){ n=read();m=read();int i,j,k;//記得給根節點賦極小值 st[0].maxx=-INF;memset(a,-0x3f,sizeof(a)); for(i=1;i<=n;i++) a[i+1]=read(); for(i=1;i<=n+2;i++) id[i]=i; build(1,n+2,0);cnt=n+2;root=(n+3)>>1; int tot,val; char ch[10]; while(m--) {//操作跟著題目要求走就好了 scanf("%s",ch); if(ch[0]!=M||ch[2]!=X)k=read(),tot=read(); if(ch[0]==I) insert(k,tot); if(ch[0]==D) erase(k,tot); if(ch[0]==M) { if(ch[2]==X)printf("%d\n",st[root].maxx); else val=read(),cov(k,tot,val); } if(ch[0]==R)rev(k,tot); if(ch[0]==G)query(k,tot); } return 0; }

NOI2005 維護數列 lg2042