由於要講座,隨便寫一下,等講完有時間好好寫一篇splay的博客。html
先直接上題目而後貼代碼,具體講解都寫代碼裏了。c++
參考的博客等的連接都貼代碼裏了,有空再好好寫。ide
請寫一個程序,要求維護一個數列,支持如下 6 種操做:(請注意,格式欄 中的下劃線‘ _ ’表示實際輸入文件中的空格)ui
輸入格式:spa
輸入文件的第 1 行包含兩個數 N 和 M,N 表示初始時數列中數的個數,M 表示要進行的操做數目。 第 2 行包含 N 個數字,描述初始時的數列。 如下 M 行,每行一條命令,格式參見問題描述中的表格.net
輸出格式:code
對於輸入數據中的 GET-SUM 和 MAX-SUM 操做,向輸出文件依次打印結 果,每一個答案(數字)佔一行。htm
9 8 2 -6 3 5 1 -5 -3 6 3 GET-SUM 5 4 MAX-SUM INSERT 8 3 -5 7 2 DELETE 12 1 MAKE-SAME 3 3 2 REVERSE 3 6 GET-SUM 5 4 MAX-SUM
-1 10 1 10
你能夠認爲在任什麼時候刻,數列中至少有 1 個數。blog
輸入數據必定是正確的,即指定位置的數在數列中必定存在。隊列
50%的數據中,任什麼時候刻數列中最多含有 30 000 個數;
100%的數據中,任什麼時候刻數列中最多含有 500 000 個數。
100%的數據中,任什麼時候刻數列中任何一個數字均在[-1 000, 1 000]內。
100%的數據中,M ≤20 000,插入的數字總數不超過 4 000 000 。
代碼:
1 /* 2 https://www.luogu.org/problemnew/show/P2042 3 https://www.luogu.org/problemnew/solution/P2042 4 https://baijiahao.baidu.com/s?id=1613228134219334653&wfr=spider&for=pc 5 https://www.cnblogs.com/victorique/p/8478866.html 6 https://www.cnblogs.com/noip/archive/2013/05/31/3111169.html 7 https://baike.baidu.com/item/%E4%BC%B8%E5%B1%95%E6%A0%91/7003945?fr=aladdin 8 https://blog.csdn.net/changtao381/article/details/8936765 9 https://blog.csdn.net/huzujun/article/details/81394092 10 */ 11 12 //插入 刪除 修改 翻轉 求和 最大的子序列 13 #include<bits/stdc++.h> 14 using namespace std; 15 typedef long long ll; 16 const int maxn=1e6+10; 17 const int inf=0x3f3f3f3f; 18 19 int n,m,rt,cnt; 20 int a[maxn],id[maxn],fa[maxn],tree[maxn][2]; 21 int sum[maxn],sz[maxn],val[maxn],mx[maxn],lx[maxn],rx[maxn]; 22 int tag[maxn],rev[maxn]; 23 //tag 是否有統一修改的標記,rev 是否有統一翻轉的標記 24 25 queue<int> q; 26 27 void pushup(int x)//分治,相似線段樹的區間合併,可是由於當前節點也有值,因此要加上當前節點的val 28 { 29 int l=tree[x][0],r=tree[x][1]; 30 sum[x]=sum[l]+sum[r]+val[x]; 31 sz[x]=sz[l]+sz[r]+1; 32 lx[x]=max(lx[l],sum[l]+lx[r]+val[x]); 33 rx[x]=max(rx[r],sum[r]+rx[l]+val[x]); 34 mx[x]=max(max(mx[l],mx[r]),rx[l]+lx[r]+val[x]);//區間最大子段和 35 } 36 37 void pushdown(int x) 38 { 39 int l=tree[x][0],r=tree[x][1]; 40 if(tag[x]){//有統一修改的標記,翻轉就沒有意義了 41 rev[x]=tag[x]=0; 42 if(l) tag[l]=1,val[l]=val[x],sum[l]=sz[l]*val[x]; 43 if(r) tag[r]=1,val[r]=val[x],sum[r]=sz[r]*val[x]; 44 if(val[x]>=0){ 45 if(l) lx[l]=rx[l]=mx[l]=sum[l]; 46 if(r) lx[r]=rx[r]=mx[r]=sum[r]; 47 } 48 else{ 49 if(l) lx[l]=rx[l]=0,mx[l]=val[x]; 50 if(r) lx[r]=rx[r]=0,mx[r]=val[x]; 51 } 52 } 53 if(rev[x]){ 54 rev[x]=0;rev[l]^=1;rev[r]^=1; 55 swap(lx[l],rx[l]);swap(lx[r],rx[r]);//注意,在翻轉操做中,先後綴的最長上升子序列都反過來了,很容易錯 56 swap(tree[l][0],tree[l][1]);swap(tree[r][0],tree[r][1]); 57 } 58 } 59 60 void rotate(int x,int &k) 61 { 62 int y=fa[x],z=fa[y],l=(tree[y][1]==x),r=l^1; 63 if(y==k) k=x; 64 else tree[z][tree[z][1]==y]=x; 65 fa[tree[x][r]]=y;fa[y]=x;fa[x]=z;//改變父子關係。爸爸變兒子,爺爺變爸爸 66 tree[y][l]=tree[x][r];tree[x][r]=y; 67 pushup(y);pushup(x);//旋轉操做,改變關係以後標記上傳 68 } 69 70 /* 71 伸展操做,三種狀態: 72 1.x的爸爸y是目標狀態,直接翻轉x 73 2.x有爸爸y,有爺爺z,若是三點在一條直線上,就先翻轉爸爸y,這樣翻轉是雙旋,保持平衡(關於旋轉 雙旋、單旋,講一下) 74 3.x有爸爸y,有爺爺z,三點不在一條直線上,直接翻轉兩次x就能夠 75 */ 76 void splay(int x,int &k)//伸展操做,核心操做 77 { 78 while(x!=k){//一直到轉到目標狀態 79 int y=fa[x],z=fa[y]; 80 if(y!=k){//若是爸爸不是目標狀態 81 if((tree[z][0]==y)^(tree[y][0]==x)) rotate(x,k);//若是三點不在一條直線上,直接轉本身 82 else rotate(y,k); 83 } 84 rotate(x,k); 85 } 86 } 87 88 /* 89 查找操做,核心操做之二 90 區間翻轉和插入以及刪除的操做都須要find操做 91 由於維護的區間的實際編號是不連續的,因此須要查找要操做的區間對應平衡樹的中序遍歷的那段區間 92 */ 93 int find(int x,int rk)//找排名第rk的 94 { 95 pushdown(x);//由於全部操做都是須要find,因此在這裏標記下傳就能夠 96 int l=tree[x][0],r=tree[x][1]; 97 if(sz[l]+1==rk) return x;//就是二叉樹的搜索操做 98 if(sz[l] >=rk) return find(l,rk); 99 else return find(r,rk-sz[l]-1); 100 101 } 102 103 /* 104 這道題極限是4*10^6*log(2*10^4),2爲底,二分,因此4*10^10,128MB差很少存10^8,爆內存 105 用時間換空間的回收冗餘編號機制 106 */ 107 void recycle(int x)//垃圾回收,節省內存,由於內存開銷太大,容易爆內存,記錄用過可是已經刪除的節點的編號,新建節點的時候直接從隊列或者棧中取出來用就能夠。時間換空間 108 { 109 int &l=tree[x][0],&r=tree[x][1]; 110 if(l) recycle(l); 111 if(r) recycle(r);//垃圾回收,一直回收到底 112 q.push(x); 113 fa[x]=tag[x]=rev[x]=l=r=0; 114 } 115 116 /* 117 核心操做之三 118 經過split 找到[k+1,k+tot],而後把k,k+tot+1移到根和右兒子的位置 119 而後返回這個右兒子的左兒子,就是要操做的區間 120 */ 121 int split(int k,int tot) 122 { 123 int x=find(rt,k),y=find(rt,k+tot+1); 124 splay(x,rt);splay(y,tree[x][1]); 125 return tree[y][0]; 126 } 127 128 void query(int k,int tot)//區間最大子段和 129 { 130 int x=split(k,tot); 131 printf("%d\n",sum[x]); 132 } 133 134 void modify(int k,int tot,int value)//當前數列第k個開始連續tot個統一修改成value 135 { 136 int x=split(k,tot),y=fa[x]; 137 val[x]=value;tag[x]=1;sum[x]=sz[x]*value; 138 if(value>=0) lx[x]=rx[x]=mx[x]=sum[x]; 139 else lx[x]=rx[x]=0,mx[x]=value;//最大的子段和就是一個,由於是負數 140 pushup(y);pushup(fa[y]);//每一步的修改操做,父子關係發生變化,記錄標記發生變化,因此要及時標記上傳 141 } 142 143 void rever(int k,int tot)//當前數列第k個開始的tot個數字翻轉 144 { 145 int x=split(k,tot),y=fa[x]; 146 if(!tag[x]){ 147 rev[x]^=1; 148 swap(tree[x][0],tree[x][1]); 149 swap(lx[x],rx[x]); 150 pushup(y);pushup(fa[y]); 151 } 152 } 153 154 void erase(int k,int tot)//當前數列第k個數字開始連續刪除tot個數字 155 { 156 int x=split(k,tot),y=fa[x]; 157 recycle(x);tree[y][0]=0; 158 pushup(y);pushup(fa[y]); 159 } 160 161 void build(int l,int r,int f) 162 { 163 int m=(l+r)>>1,now=id[m],pre=id[f]; 164 if(l==r){ 165 mx[now]=sum[now]=a[l]; 166 tag[now]=rev[now]=0;//這裏的清零操做是必要的,由於多是以前垃圾回收,冗餘的 167 lx[now]=rx[now]=max(a[l],0); 168 sz[now]=1; 169 } 170 if(l<m) build(l,m-1,m); 171 if(r>m) build(m+1,r,m); 172 val[now]=a[m];fa[now]=pre; 173 pushup(now); 174 tree[pre][m>=f]=now;//插入右或者左區間 175 } 176 177 void insert(int k,int tot)//當前數列第k個數字後插入tot個數字 178 { 179 for(int i=1;i<=tot;i++){ 180 scanf("%d",&a[i]); 181 } 182 for(int i=1;i<=tot;i++){ 183 if(!q.empty()) id[i]=q.front(),q.pop(); 184 else id[i]=++cnt;//利用隊列裏的冗餘節點編號 185 } 186 build(1,tot,0);//將讀入的tot個數建成一個平衡樹 187 int z=id[(1+tot)>>1];//取中點爲根 188 int x=find(rt,k+1),y=find(rt,k+2);//首先,根據中序遍歷,找到要操做的區間的實際編號 189 splay(x,rt);splay(y,tree[x][1]);//把k+1(注意咱們已經右移了一個單位)和(k+1)+1移到根和右兒子 190 fa[z]=y;tree[y][0]=z;//直接把須要插入的這個平衡樹掛到右兒子的左兒子上去就行了 191 pushup(y);pushup(x); 192 } 193 194 //對於具體在哪裏上傳標記和下傳標記 195 //能夠這麼記,只要用了split就要從新上傳標記 196 //只有find中須要下傳標記 197 //但其實,你多傳幾回是沒有關係的,可是少傳了就不行了 198 int main() 199 { 200 scanf("%d%d",&n,&m); 201 mx[0]=a[1]=a[n+2]=-inf;//兩個虛擬節點,哨兵節點 202 for(int i=1;i<=n;i++){ 203 scanf("%d",&a[i+1]); 204 } 205 for(int i=1;i<=n+2;i++){//虛擬了兩個節點1和n+2,而後把須要操做區間總體右移一個單位 206 id[i]=i; 207 } 208 build(1,n+2,0); 209 rt=(n+3)>>1;cnt=n+2;//取最中間的爲根,這樣就是一個完美的平衡樹 210 int k,tot,value;char op[10]; 211 for(int i=1;i<=m;i++){ 212 scanf("%s",op); 213 if(op[0]!='M'||op[2]!='X') scanf("%d%d",&k,&tot); 214 if(op[0]=='I') insert(k,tot); 215 if(op[0]=='D') erase(k,tot); 216 if(op[0]=='M'){ 217 if(op[2]=='X') printf("%d\n",mx[rt]); 218 else scanf("%d",&value),modify(k,tot,value); 219 } 220 if(op[0]=='R') rever(k,tot); 221 if(op[0]=='G') query(k,tot); 222 } 223 return 0; 224 }