首页 > 代码库 > poj 3468 A Simple Problem with Integers(原来是一道简单的线段树区间修改用来练练splay)

poj 3468 A Simple Problem with Integers(原来是一道简单的线段树区间修改用来练练splay)

题目链接:http://poj.org/problem?id=3468

 

题解:splay功能比线段树强大当然代价就是有些操作比线段树慢,这题用splay实现的比线段树慢上一倍。线段树用lazy标记差不多要2s用splay要4s。可以用splay来实现线段树的区间操作更深层次的了解一下splay算是入个门。

#include <iostream>#include <cstring>#include <cmath>#include <cstdlib>#include <cstdio>using namespace std;const int M = 1e5 + 10;typedef long long ll;int pre[M] , ch[M][2] , size[M] , root , tot;//分别表示父节点,左右儿子,大小,根节点,总共的节点数。int key[M];//当前节点对应的权值int add[M];//类似懒惰标记ll sum[M];//当前节点包括他以下的节点权值的总和可以理解为子树权值之和加上这个节点的权值int a[M];int n , q;void NewNode(int &r , int fa , int k) {    r = ++tot;    pre[r] = fa;    size[r] = 1;    key[r] = k;    add[r] = 0;    sum[r] = 0;    ch[r][0] = ch[r][0] = 0;}//标准的初始化节点void update(int r , int ad) {    if(r == 0) return;    add[r] += ad;    key[r] += ad;    sum[r] += (ll)ad * size[r];}//节点更新void push_up(int r) {    size[r] = size[ch[r][0]] + size[ch[r][1]] + 1;    sum[r] = sum[ch[r][0]] + sum[ch[r][1]] + key[r];}void push_down(int r) {    if(add[r]) {        update(ch[r][0] , add[r]);        update(ch[r][1] , add[r]);        add[r] = 0;    }}//一系列类似线段树的操作。void Rotate(int x , int kind) {    int y = pre[x];    push_down(y);    push_down(x);    ch[y][!kind] = ch[x][kind];    pre[ch[x][kind]] = y;    if(pre[y]) ch[pre[y]][ch[pre[y]][1] == y] = x;    pre[x] = pre[y];    ch[x][kind] = y;    pre[y] = x;    push_up(y);}void Splay(int r , int goal) {    push_down(r);    while(pre[r] != goal) {        if(pre[pre[r]] == goal) Rotate(r , ch[pre[r]][0] == r);        else {            int y = pre[r];            int kind = (ch[pre[y]][0] == y);            if(ch[y][kind] == y) {                Rotate(r , !kind);                Rotate(r , kind);            }            else {                Rotate(y , kind);                Rotate(r , kind);            }        }    }    push_up(r);    if(goal == 0) root = r;}//一系列标准的splay的操作void build(int &x , int l , int r , int fa) {    if(l > r) return ;    int mid = (l + r) >> 1;    NewNode(x , fa , a[mid]);    build(ch[x][0] , l , mid - 1 , x);    build(ch[x][1] , mid + 1 , r , x);    push_up(x);}void init() {    root = 0 , tot = 0;    ch[root][0] = ch[root][1] = pre[root] = size[root] = add[root] = sum[root] = key[root] = 0;    NewNode(root , 0 , -1);    NewNode(ch[root][1] , root , -1);    build(ch[ch[root][1]][0] , 1 , n , ch[root][1]);    push_up(root);    push_up(ch[root][1]);}//这里之所以要优先建两个点和后面的更新有关int get_kth(int r , int k) {    push_down(r);    int t = size[ch[r][0]] + 1;    if(t == k) return r;    else if(t > k) return get_kth(ch[r][0] , k);    else return get_kth(ch[r][1] , k - t);}//获得第几大的数void ADD(int l , int r , int ad) {    Splay(get_kth(root , l) , 0);    Splay(get_kth(root , r + 2) , root);    update(ch[ch[root][1]][0] , ad);    push_up(ch[root][1]);    push_up(root);}//这里按照常理应该是将第l-1个节点移到根然后再将r+1的节点移到根的右儿子那么(l~r)就是r+1节点的左儿子的sum值由于之前加了两个节点所以变到了l~r+2,毕竟l-1可能为0就是就是根节点处理起来可能会有些不便。当然无视也行,按照个人喜好来就行。long long query(int l , int r) {    Splay(get_kth(root , l) , 0);    Splay(get_kth(root , r + 2), root);    return sum[ch[ch[root][1]][0]];}//区间查询同理int main() {    while(scanf("%d%d" , &n , &q) == 2) {        for(int i = 1 ; i <= n ; i++) scanf("%d" , &a[i]);        init();        while(q--) {            char c[10];            scanf("%s" , c);            if(c[0] == Q) {                int l , r;                scanf("%d%d" , &l , &r);                printf("%lld\n" , query(l , r));            }            else {                int l , r , x;                scanf("%d%d%d" , &l , &r , &x);                ADD(l , r , x);            }        }    }    return 0;}

 

poj 3468 A Simple Problem with Integers(原来是一道简单的线段树区间修改用来练练splay)