BZOJ1901 动态排名 线段树套平衡树

(同步个人博客 http://sxysxy.org/blogs/14 到csdn)

动态区间第k小,http://www.lydsy.com/JudgeOnline/problem.php?id=1901

权限题所以大致描述下题面:

初始有一个长度有n得序列a1,a2…an,要求支持查询序列下标区间l, r内第k小的数,支持修改序列内某个数。序列长度n <= 10000(这里我用来练习线段树套平衡树,于是就这样做了。n <= 10000说不定分块就过了…)

做法:

对序列1..n构造线段树,线段树上每一个节点包含一棵平衡树,这棵平衡树包含了当前线段树节点内所有的数。

对平衡树的要求:速度快(显然),支持插入删除(删除是需要修改的时候用的),要支持名次树(再准确点就是支持对于一个x查询<=x的数的个数)。这里我用了AVL树(关于AVL树:http://sxysxy.org/blogs/13)

构造:

构造很简单。构造线段树的同时,把每个线段树节点管理的区间内的所有数插入平衡树。

查询

对于查询”l,r内第k小的数”,使用二分法,二分l, r内第k小的数为m,然后向线段树查询l,r内小于等于m的数的个数(线段数里面套了平衡树,里面平衡树可以查询名次,外面线段树管理区间)。

修改

修改其实是删除+插入。设原来第p个数为u,要将他修改为v,那么只需要线段树内,所有包含位置p的线段树节点内的平衡树删除u,再插入v,即可。

实现上的细节问题见代码
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <cstring>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <list>
#include <queue>
#include <list>
using namespace std;

//AVL平衡树
typedef struct _AVL
{
    struct _AVL *left, *right;
    int data;
    int height;
    int size;
}AVLNode, *pNode, *AVLTree;
#define HEIGHT(x) ((x)?(x)->height:-1)
#define SIZE(x) ((x)?(x)->size:0)
void maintain(pNode k)
{
    if(k)
    {
        k -> height = max(HEIGHT(k -> left), HEIGHT(k -> right)) + 1;
        k -> size = SIZE(k -> left) + SIZE(k -> right) + 1;
    }
} 
pNode singleRotateLL(pNode k)
{
    pNode k1 = k -> left;
    k -> left = k1 -> right;
    k1 -> right = k;
    maintain(k);
    maintain(k1);
    return k1;
}
pNode singleRotateRR(pNode k)
{
    pNode k1 = k -> right;
    k -> right = k1 -> left;
    k1 -> left = k;
    maintain(k);
    maintain(k1);
    return k1;
}
pNode doubleRotateLR(pNode k)
{
    k -> left = singleRotateRR(k -> left);
    return singleRotateLL(k);
}
pNode doubleRotateRL(pNode k)
{
    k -> right = singleRotateLL(k -> right);
    return singleRotateRR(k);
}
AVLTree insert(AVLTree t, int x)
{
    if(!t)
    {
        t = new AVLNode;
        t -> data = x;
        t -> size = 1;
        t -> left = t -> right = 0;
    }else
    {
        if(x < t -> data)   
        {
            t -> left = insert(t -> left, x);
            if(HEIGHT(t -> left) - HEIGHT(t -> right) == 2)
            {
                if(x < t -> left -> data)
                    t = singleRotateLL(t); 
                else    
                    t = doubleRotateLR(t);    
            }
        }else 
        {
            t -> right = insert(t -> right, x);
            if(HEIGHT(t -> right) - HEIGHT(t -> left) == 2)
            {
                if(x >= t -> right -> data)
                    t = singleRotateRR(t);
                else
                    t = doubleRotateRL(t);
            }
        }
    }
    maintain(t);
    return t;
}
pNode delBalance(pNode k)
{
    if(HEIGHT(k -> right) - HEIGHT(k -> left) == 2)
    {
        if(k -> right)
        {
            if(HEIGHT(k -> right -> right) >= HEIGHT(k -> right -> left))
                k = singleRotateRR(k);
            else
                k = doubleRotateRL(k);
        }
    }
    if(HEIGHT(k -> left) - HEIGHT(k -> right) == 2)
    {
        if(k -> left)
        {
            if(HEIGHT(k -> left -> left) >= HEIGHT(k -> left -> right))
                k = singleRotateLL(k);
            else
                k = doubleRotateLR(k);
        }
    }
    maintain(k);
    return k;
}
AVLTree delNode(AVLTree t, int x)
{
    if(!t)
        return NULL;
    if(x == t -> data)
    {
        if(!t -> right)
        {
            pNode tmp = t;
            t = t -> left;
            free(tmp);
        }else
        {
            pNode tmp = t -> right;
            while(tmp -> left)
                tmp = tmp -> left;
            t -> data = tmp -> data;
            t -> right = delNode(t -> right, t -> data);
        }
        maintain(t);
        return t;
    }
    if(x < t -> data)
    {
        t -> left = delNode(t -> left, x);
    }else
    {
        t -> right = delNode(t -> right, x);
    }
    if(t -> left)
        t -> left = delBalance(t -> left);
    if(t -> right)
        t -> right = delBalance(t -> right);
    if(t)
        t = delBalance(t);
    return t;
}
//<= x 的个数
int Smaller(pNode t, int x)
{
    if(!t)return 0;
    if(x < t -> data)
        return Smaller(t -> left, x);
    else
        return 1 + SIZE(t -> left) + Smaller(t -> right, x);
}


#define MAXN 100002
int last = 0;
struct _SegNode
{
    int ls, rs;   //左右孩子
    AVLTree rt;   //这个节点的平衡树
    int l, r;  
}ns[MAXN<<1];
int data[MAXN];
int build(int l, int r)
{
    int cur = ++last;
    ns[cur].l = l;
    ns[cur].r = r;
    ns[cur].rt = NULL;

    for(int i = l; i <= r; i++) //将区间l,r内的数全部插入平衡树
        ns[cur].rt = insert(ns[cur].rt, data[i]);
    if(l == r)
        return cur;
    int mid = (l+r)>>1;
    if(l <= mid)
    {
        ns[cur].ls = build(l, mid);
        ns[cur].rs = build(mid+1, r);
    }
    return cur;
}
//查询区间l, r内 <= n的数的个数
//第一个参数c是线段树节点的下标
int queryTree(int c, int l, int r, int n)
{
    if(ns[c].l == l && ns[c].r == r)
        return Smaller(ns[c].rt, n);  // <= n的数的个数
    int ls= ns[c].ls;
    int rs = ns[c].rs;
    if(r <= ns[ls].r)   //l,r完全在左边
        return queryTree(ls, l, r, n);
    else if(l >= ns[rs].l) //右边
        return queryTree(rs, l, r, n);
    else  //跨越中点,两段之和
        return queryTree(ls, l, ns[ls].r, n) + queryTree(rs, ns[rs].l, r, n);
}
//修改第pos个数为v
void update(int c, int pos, int v)
{ 
    ns[c].rt = delNode(ns[c].rt, data[pos]);   //删掉原来的数
    ns[c].rt = insert(ns[c].rt, v);            //插入新的数
    int ls = ns[c].ls;
    int rs = ns[c].rs;
    if(ls && pos >= ns[ls].l && pos <= ns[ls].r)  //更新左右子树
        update(ls, pos, v);
    else if(rs && pos >= ns[rs].l && pos <= ns[rs].r)
        update(rs, pos, v);
}
void change(int pos, int v)
{
    update(1, pos, v);
    data[pos] = v;     //记录最后一次data[pos]的值
}
int solve(int l, int r, int k)
{
    int x = 0;
    int y = 0x7fffffff;   //二分答案
    int ans = 0;
    while(x <= y)
    {
        int mid = (x+y)>>1;
        int f = queryTree(1, l, r, mid); // l,r内 <= mid的数的个数
        if(f >= k)    
        {
            y = mid-1;
            ans = mid;
        }
        else
            x = mid+1;
    }
    return ans;
}
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);;
    for(int i = 1; i <= n; i++)
        scanf("%d", data+i); 
    build(1, n);
    while(m--)
    {
        char c;
        while(c = getchar())
            if(c == 'Q' || c == 'C')break;
        if(c == 'Q')
        {
            int a, b, c;
            scanf("%d %d %d", &a, &b, &c);
            printf("%d\n", solve(a, b, c));
        }else if(c == 'C')
        {
            int a, b;
            scanf("%d %d", &a, &b);
            change(a, b);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值