SuperMemo
Time Limit: 5000MS | Memory Limit: 65536K |
Total Submissions: 16524 | Accepted: 5214 |
Case Time Limit: 2000MS |
Description
Your friend, Jackson is invited to a TV show called SuperMemo in which the participant is told to play a memorizing game. At first, the host tells the participant a sequence of numbers, {A1, A2, ... An}. Then the host performs a series of operations and queries on the sequence which consists:
- ADD x y D: Add D to each number in sub-sequence {Ax ... Ay}. For example, performing "ADD 2 4 1" on {1, 2, 3, 4, 5} results in {1, 3, 4, 5, 5}
- REVERSE x y: reverse the sub-sequence {Ax ... Ay}. For example, performing "REVERSE 2 4" on {1, 2, 3, 4, 5} results in {1, 4, 3, 2, 5}
- REVOLVE x y T: rotate sub-sequence {Ax ... Ay} T times. For example, performing "REVOLVE 2 4 2" on {1, 2, 3, 4, 5} results in {1, 3, 4, 2, 5}
- INSERT x P: insert P after Ax. For example, performing "INSERT 2 4" on {1, 2, 3, 4, 5} results in {1, 2, 4, 3, 4, 5}
- DELETE x: delete Ax. For example, performing "DELETE 2" on {1, 2, 3, 4, 5} results in {1, 3, 4, 5}
- MIN x y: query the participant what is the minimum number in sub-sequence {Ax ... Ay}. For example, the correct answer to "MIN 2 4" on {1, 2, 3, 4, 5} is 2
To make the show more interesting, the participant is granted a chance to turn to someone else that means when Jackson feels difficult in answering a query he may call you for help. You task is to watch the TV show and write a program giving the correct answer to each query in order to assist Jackson whenever he calls.
Input
The first line contains n (n ≤ 100000).
The following n lines describe the sequence.
Then follows M (M ≤ 100000), the numbers of operations and queries.
The following M lines describe the operations and queries.
Output
For each "MIN" query, output the correct answer.
Sample Input
5
1
2
3
4
5
2
ADD 2 4 1
MIN 4 5
Sample Output
5
Source
POJ Founder Monthly Contest – 2008.04.13, Yao Jinyu
題意,模闆,思路全都轉自tabris大佬
題意:
給出一個數字序列,有6種操作:
(1) ADD x y d: 第x個數到第y個數加d 。
(2) REVERSE x y : 将區間[x,y]中的數翻轉 。
(3) REVOLVE x y t :将區間[x,y]旋轉t次,如1 2 3 4 5 旋轉2次後就變成4 5 1 2 3 。
(4) INSERT x p :在第x個數後面插入p 。
(5)DELETE x :删除第x個數 。
(6) MIN x y : 查詢區間[x,y]中的最小值 。
就是區間加,翻轉,剪切,詢問最值。點插入,删除。這幾個操作
有翻轉了 是以用SPLAY來維護一下
區間加 區間最小值就不說了 和普通的二叉搜尋樹一模一樣.
點插入 删除
假如要插入的點在x
那麼讓x-1做為樹根,x+1伸展到根節點下面,那麼x+1的左兒子就是空出來的 加個值就好了
删除發過來一樣的
區間操作
對于區間[l,r]
那麼讓l-1做為樹根,r+1伸展到根節點下面,那麼r+1的左兒子就是這個區間
但為了更好的處理[1,n]這個區間 加上個0和n+1這兩個節點
翻轉
同樣在一個二叉樹中 翻轉也就是讓每個節點的兩個兒子交換一下順序就好了,, 打個标記 就行了,
旋轉
其實旋轉說白了就是将這個區間分成兩段然後交換一下子,
是以我們可以将後一個區間處理到一個子樹上,然後放到 l−1,l 這兩個節點之間,就好了,先減掉,然後在加上去就好了
總結下:splay 的區間操作,插入操作, 通常都是把左端-1旋轉到根, 右端點+1旋轉到根的右兒子, 那樣 右端點+1 的左兒子就是區間 l-r 因為他要大于根 小于 他爸爸。 這些操作都是通過旋轉,然後讓一個節點變成一棵樹,對這顆子樹不斷操作,lazy,rev什麼的插入/删除節點的時候注意父節點要修改
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int INF = 1e9;
int n, m;
int ch[maxn][2]; //0做孩子, 1右孩子
int f[maxn]; //每個節點的父親
int sz[maxn]; //每個節點為根子樹的大小
int val[maxn]; //這個節點所表示的值
int cnt[maxn]; //這個節點所表示值的數量
int mi[maxn]; //這個節點子樹的最小值
int rev[maxn]; //反轉标記
int lazy[maxn]; //延遲标記
int root; // splay的根
int tot; //樹所有的節點數量
void Swap(int &x, int &y)
{
x ^=y; y ^= x; x ^= y;
}
int Min(int x, int y)
{
return x < y ? x : y;
}
void update_rev(int x) //更新反轉
{
if(!x) return;
Swap(ch[x][0], ch[x][1]);
rev[x] ^= 1; //如果這一層曾經被轉過下面就不用轉了, 把rev取消了
}
void update_add(int x, int v)
{
if(x) lazy[x] += v, val[x] += v, mi[x] += v;
}
void newnode(int rt, int v, int fa)
{
f[rt] = fa; sz[rt] = 1;
val[rt] = mi[rt] = v;
ch[rt][0] = ch[rt][1] = rev[rt] = lazy[rt] = 0; //加點的時候把所有的資訊都更新了
}
void delnode(int rt) //為了回收空間,其實沒什麼太大的用處
{
f[rt] = val[rt] = sz[rt] = mi[rt] = 0;
ch[rt][0] = ch[rt][1] = rev[rt] = lazy[rt] = 0;
}
void pushup(int x) //跟線段樹一樣,從下往上不斷更新
{
if(!x)return ;
sz[x] = 1, mi[x] = val[x];
if(ch[x][0]) sz[x] += sz[ch[x][0]], mi[x]= Min(mi[x],mi[ch[x][0]]); //更新個數跟目前子樹最小值
if(ch[x][1]) sz[x] += sz[ch[x][1]], mi[x]= Min(mi[x],mi[ch[x][1]]);
}
void pushdown(int x) //向下傳遞lazy 跟 rev
{
if(!x) return;
if(lazy[x])
{
update_add(ch[x][0], lazy[x]);
update_add(ch[x][1], lazy[x]);
lazy[x] = 0;
}
if(rev[x])
{
update_rev(ch[x][0]);
update_rev(ch[x][1]);
rev[x] = 0;
}
}
void build(int &rt, int l, int r, int fa) //rt是節點編号,節點的大小代表了兩個數位置的相對順序
{ //一共tot個節點
if(l > r) return;
int mid = (r+l) >> 1;
rt = mid; newnode(rt, val[rt], fa);
build(ch[rt][0], l, mid-1, rt);
build(ch[rt][1], mid+1, r, rt);
pushup(rt);
}
void Rotate(int x, int k) // k = 0左旋, k = 1右旋
{
int y = f[x];int z = f[y];
pushdown(y); pushdown(x);
ch[y][!k] = ch[x][k];
if(ch[x][k]) f[ch[x][k]] = y;
f[x] = z;
if(z) ch[z][ch[z][1]==y] = x;
f[y] = x; ch[x][k] = y;
pushup(y), pushup(x);
}
void splay(int x, int goal)
{
pushdown(x);
while(f[x] != goal)
{
int y = f[x], z = f[y];
//在這裡下傳翻轉标記,在rotate裡下傳标記可能會使樹形改變導緻旋轉出錯
pushdown(z); pushdown(y); pushdown(x);
if(f[y] == goal) Rotate(x, ch[y][0] == x);
else
{
int p = ch[f[y]][0] == y;
if(ch[y][p] == x) Rotate(x, !p), Rotate(x, p);
else Rotate(y, p), Rotate(x, p);
}
}
pushup(x);
if(goal == 0) root = x;
}
//以x為根的子樹 的極值點 0 極小 1 極大
int extreme(int x,int k)
{
while(ch[x][k]) x = ch[x][k];
splay(x, 0); //所有操作之後都伸展下
return x;
}
//以節點編号x為根的子樹 第k個數的節點編号
int kth(int x,int k)
{
pushdown(x);
if(sz[ch[x][0]]+1 == k) return x;
else if(sz[ch[x][0]] >= k) return kth(ch[x][0],k);
else return kth(ch[x][1], k-sz[ch[x][0]]-1);
}
//區間交換
void exchange(int l1,int r1,int l2,int r2)
{
int x = kth(root, l2-1), y = kth(root, r2+1);
splay(x,0), splay(y,x);
int tmp_right = ch[y][0]; ch[y][0]=0; //“剪貼下來”
x = kth(root, l1-1),y = kth(root, l1);
splay(x,0), splay(y,x);
ch[y][0] = tmp_right;
f[tmp_right] = y;
}
//區間翻轉
void reversal(int l,int r)
{
int x = kth(root,l-1),y = kth(root,r+1);
splay(x,0); splay(y,x);
update_rev(ch[y][0]); //ch[y][0]就是l-r區間
}
//區間加
void add(int l,int r,int v)
{
int x = kth(root,l-1), y = kth(root,r+1);
// cout << 1 <<endl;
splay(x,0); splay(y,x);
update_add(ch[y][0],v); //ch[y][0]就是l-r區間
}
//在第k個數後插入值為x的節點
void Insert(int k,int x){
int r = kth(root, k),rr = kth(root, k+1);
splay(r,0), splay(rr,r);
newnode(++tot, x, rr); ch[rr][0] = tot; //節點個數增加
for(r = rr; r ; r = f[r]) pushdown(r), pushup(r);
splay(rr,0);
}
//删除第k位置的數
void Delete(int k)
{
splay(kth(root,k-1), 0);
splay(kth(root,k+1), root);
delnode(ch[ch[root][1]][0]);
ch[ch[root][1]][0] = 0;
pushup(ch[root][1]);
pushup(root);
}
// 擷取區間最大值
//int get_max(int l,int r)
//{
// int x = kth(root,l-1), y = kth(root,r+1);
// splay(x,0); splay(y,x);
// return mx[ch[y][0]];
//}
//擷取區間最小值
int get_min(int l,int r)
{
int x = kth(root,l-1), y = kth(root,r+1);
splay(x,0); splay(y,x);
return mi[ch[y][0]];
}
void init(int n)
{
root = 0;
//不斷更新的, 不斷插入的, 需要一個tot記錄插入節點的編号
// tot = 0;
// newnode(++tot, -INF, 0);
// newnode(++tot, INF, root);
// ch[root][1] = tot;
f[0] = sz[0] = ch[0][0] = ch[0][1] = rev[0] = lazy[0] = 0; //rt編号多加兩個,處理區間[1,n]
build(root, 1, n, 0);
pushup(root);
}
char s[12];
int main()
{
scanf("%d", &n);
val[1] = val[n+2] = INF; //多加兩個編号0,n+1, 把區間1-n包起來
for(int i = 2;i <= n+1; i++) scanf("%d", &val[i]);
tot = n+2;
init(n+2);
scanf("%d",&m);
for(int i = 1; i <= m; i++){
int d, l, r;
scanf(" %s",s);
if(s[0]=='A')
{ //ADD
scanf("%d%d%d", &l, &r, &d);
add(l+1,r+1,d);
}
else if(s[0]=='I')
{ //INSERT
scanf("%d%d",&l,&d);
Insert(l+1,d);
}
else if(s[0]=='M')
{ //MIN
scanf("%d%d",&l,&r);
printf("%d\n",get_min(l+1,r+1));
}
else if(s[0]=='D')
{ //DELETE
scanf("%d",&l);
Delete(l+1);
}
else if(s[3]=='E')
{ //REVERSE
scanf("%d%d",&l,&r);
reversal(l+1, r+1); //增加了1一個節點全體後移一個
}
else
{ //REVOLVE
scanf("%d%d%d",&l,&r,&d);
d = d % (r-l+1);
if(d) exchange(l +1,r-d +1,r-d+1 +1,r +1);
}
}
return 0;
}