关于实时开节点的可持久化线段树及区间/树链第K大值

又扯回这道经典的可修改区间第K小值 Dynamic Ranking了:

http://www.lydsy.com:808/JudgeOnline/problem.php?id=1901


一、树套树

之前写过,挺慢的,也挺长的。


二、树状数组套权值线段树


如果没有修改,可以直接开n棵权值线段树,每一棵线段树都是在前一棵的基础上只修改一位。

每一棵线段树也都是每一个前缀所代表的“权值线段树”。

所以两棵线段树相减代表的就是一个区间的"权值线段树",就能在上面二分。


但是有修改了,每一次修改就会影响它后面的所有线段树。

所以使用BIT来维护这个前缀和,每次只会修改O(logn)棵,就没问题了。

每个区间也通过O(logn)棵线段树的加加减减得到。


现在来计算时间复杂度:

每次修改影响O(logn)课线段树,每棵线段树修改需要影响O(logn)个节点,所以是O(log^2n)的。

而线段树的节点也可以动态的开,每次修改需要的空间也是O(log^2n)级别的。


Code1:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include <map>
using namespace std;
#define N 10005
#define M 20005
int n , m , a[N];
int d[M] , D;
pair<int , int> w[N];
int r[N];

int c[N];

#define Node 2097152
#define MID int mid = l + r >> 1
#define Left l , mid
#define Right mid + 1 , r
int ch[Node][2] , sum[Node] , nodecnt;

int newnode()
{
    ++ nodecnt;
    ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0;
    return nodecnt;
}

void add(int& p , int l , int r , int x , int w)
{
    if (!p) p = newnode();
    if (l == r)
        sum[p] += w;
    else
    {
        MID;
        if (x <= mid)
            add(ch[p][0] , Left , x , w);
        else
            add(ch[p][1] , Right , x , w);
        sum[p] = sum[ch[p][0]] + sum[ch[p][1]];
    }
}

void add(int x , int w , int val)
{
    for (int i = x ; i <= n ; i += i & -i)
        add(c[i] , 1 , D , w , val);
}
int PP[20] , MM[20] , sp , sm;
int query(int l , int r , int K)
{
    if (l == r) return l; MID;
    int cnt = 0;
    for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][0]];
    for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][0]];
    if (cnt >= K)
    {
        for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0];
        for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0];
        return query(Left , K);
    }
    else
    {
        for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1];
        for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1];
        return query(Right , K - cnt);
    }
}
void work()
{
    int i , j , k , x , y;
    char str[5];
    scanf("%d%d",&n,&m);
    for (i = 1 ; i <= n ; ++ i)
        scanf("%d",&a[i]) , d[D ++] = a[i];
    for (i = 1 ; i <= m ; ++ i)
    {
        scanf("%s%d%d",str,&x,&y);
        if (*str == 'Q')
            scanf("%d",&k);
        else k = 0 , d[D ++] = y;
        w[i] = make_pair(x , y) , r[i] = k;
    }
    sort(d , d + D) , D = unique(d , d + D) - d;
    for (i = 1 ; i <= n ; ++ i)
    {
        a[i] = lower_bound(d , d + D , a[i]) - d + 1;
        add(i , a[i] , 1);
    }
    for (i = 1 ; i <= m ; ++ i)
    {
        x = w[i].first , y = w[i].second;
        if (r[i])
        {
            sp = 0 ; for (j = y ; j ; j -= j & -j) PP[sp ++] = c[j];
            sm = 0 ; for (j = x-1 ; j ; j -= j & -j) MM[sm ++] = c[j];
            printf("%d\n" , d[query(1 , D , r[i]) - 1]);
        }
        else
        {
            y = lower_bound(d , d + D , y) - d + 1;
            add(x , a[x] , -1);
            a[x] = y;
            add(x , a[x] , 1);
        }
    }
}

int main()
{
    work();
    return 0;
}

以上代码是离散化的,但如果强制在线就无法对新修改的权值进行离散化。

但实际上由于线段树的节点是完全动态开的,它的range完全可以设为数字的值域。

这样只会增加线段树的时空常数,从O(logn)变为O(log(range)),能简化不少代码。

但空间是很宝贵的……能离散化的时候尽量离散化吧。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include <map>
using namespace std;
#define N 10005
int n , m , a[N];
int D = 1e9;
int c[N];
#define Node 8388608
#define MID int mid = l + r >> 1
#define Left l , mid
#define Right mid + 1 , r
int ch[Node][2] , sum[Node] , nodecnt;

int newnode()
{
    ++ nodecnt;
    ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0;
    return nodecnt;
}

void add(int& p , int l , int r , int x , int w)
{
    if (!p) p = newnode();
    if (l == r)
        sum[p] += w;
    else
    {
        MID;
        if (x <= mid)
            add(ch[p][0] , Left , x , w);
        else
            add(ch[p][1] , Right , x , w);
        sum[p] = sum[ch[p][0]] + sum[ch[p][1]];
    }
}

void add(int x , int w , int val)
{
    for (int i = x ; i <= n ; i += i & -i)
        add(c[i] , 0 , D , w , val);
}
int PP[50] , MM[50] , sp , sm;
int query(int l , int r , int K)
{
    if (l == r) return l; MID;
    int cnt = 0;
    for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][0]];
    for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][0]];
    if (cnt >= K)
    {
        for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0];
        for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0];
        return query(Left , K);
    }
    else
    {
        for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1];
        for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1];
        return query(Right , K - cnt);
    }
}
void work()
{
    int i , j , k , x , y;
    char str[5];
    scanf("%d%d",&n,&m);
    for (i = 1 ; i <= n ; ++ i)
        scanf("%d",&a[i]) , add(i , a[i] , 1);
    for (i = 1 ; i <= m ; ++ i)
    {
        scanf("%s%d%d",str,&x,&y);
        if (*str == 'Q')
        {
            scanf("%d",&k);
            sp = 0 ; for (j = y ; j ; j -= j & -j) PP[sp ++] = c[j];
            sm = 0 ; for (j = x-1 ; j ; j -= j & -j) MM[sm ++] = c[j];
            printf("%d\n" , query(0 , D , k));
        }
        else
        {
            add(x , a[x] , -1);
            a[x] = y;
            add(x , a[x] , 1);
        }
    }
}

int main()
{
    work();
    return 0;
}

Query过程写的比较暴力随性……怎么方便怎么来……

然后今天推翻一切自己重写才发现出乎意料地好想好写……原来写的那是什么玩意啊 - -



再记一下关于树上链第K大值的体会……

http://www.lydsy.com:808/JudgeOnline/problem.php?id=1146

前几天刚写完暴力的树链剖分线段树套平衡树 = = 

修改查询的复杂度分别是O(log^2n)和O(log^4n),很吓人。


也能使用可持久化线段树卖空间来提高时间效率……

每棵线段树记录的是它到根所有经过的节点的权值。

这样一条从x到y的链就可以用X+Y-LCA(x,y)-father(LCA(x,y))得到(这里是点权)。

考虑修改,修改一个点权影响的是它的子树,考虑DFS序列就是一个区间。

所以使用另外一棵线段树来维护DFS序列,实现区间增和单点查询,这个是很好做的,也不用增加标记。

每一条从根出发的链的"权值线段树"都能利用这棵线段树,成为O(logn)棵"权值线段树"的和,对"权值线段树"查询修改也需要O(logn)时间。

询问和修改的时空复杂度就也是O(log^2n)级别的。


这道题空间非常紧……需要卡好空间限制才能过,如果不先离散化的话。


Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")
using namespace std;
#define N 80005
int n , m , a[N] , pre[N] , mcnt;
struct edge
{
    int x , next;
}e[N << 1];
int dep[N] , f[17][N] , L[N] , R[N] , ncnt;
void dfs(int x , int fa)
{
    f[0][x] = fa , dep[x] = dep[fa] + 1 , L[x] = ++ ncnt;
    for (int i = pre[x] ; ~i ; i = e[i].next)
        if (e[i].x != fa)
            dfs(e[i].x , x);
    R[x] = ncnt;
}
int LCA(int x , int y)
{
    if (dep[x] < dep[y]) swap(x , y);
    for (int i = 16 ; i >= 0 ; -- i)
        if (dep[x] - dep[y] >> i & 1)
            x = f[i][x];
    if (x == y) return y;
    for (int i = 16 ; i >= 0 ; -- i)
        if (f[i][x] != f[i][y])
            x = f[i][x] , y = f[i][y];
    return f[0][x];
}

int Down = -1 , Up = 1e8 , t[N << 1];
#define Node 12582912
int id(int l , int r) {return l + r | l != r;}
#define MID int mid = l + r >> 1
#define Left l , mid
#define Right mid + 1 , r
int ch[Node][2] , sum[Node] , nodecnt;

int newnode()
{
    ++ nodecnt;
    ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0;
    return nodecnt;
}

void add(int& p , int l , int r , int x , int w)
{
    if (!p) p = newnode();
    if (l == r)
        sum[p] += w;
    else
    {
        MID;
        if (x <= mid)
            add(ch[p][0] , Left , x , w);
        else
            add(ch[p][1] , Right , x , w);
        sum[p] = sum[ch[p][0]] + sum[ch[p][1]];
    }
}
int PP[100] , MM[100] , sp , sm;
int query(int l , int r , int K)
{
    if (l == r) return l; MID;
    int cnt = 0;
    for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][1]];
    for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][1]];
    if (cnt >= K)
    {
        for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1];
        for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1];
        return query(Right , K);
    }
    else
    {
        for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0];
        for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0];
        return query(Left , K - cnt);
    }
}
void ADD(int l , int r , int top , int bot , int x , int w)
{
    int p = id(l , r);
    if (top <= l && r <= bot)
        add(t[p] , Down , Up , x , w);
    else
    {
        MID;
        if (top <= mid)
            ADD(Left , top , bot , x , w);
        if (bot > mid)
            ADD(Right , top , bot , x , w);
    }
}
void QUERY(int l , int r , int x , bool flag)
{
    int p = id(l , r);
    if (flag) PP[sp ++] = t[p]; else MM[sm ++] = t[p];
    if (l != r)
    {
        MID;
        if (x <= mid)
            QUERY(Left , x , flag);
        else
            QUERY(Right , x , flag);
    }
}
void work()
{
    int i , j , k , x , y , z;
    scanf("%d%d",&n,&m);
    for (i = 1 ; i <= n ; ++ i)
        scanf("%d",&a[i]);
    memset(pre , -1 , sizeof(pre)) , mcnt = 0;
    for (i = 1 ; i < n ; ++ i)
    {
        scanf("%d%d",&x,&y);
        e[mcnt] = (edge) {y , pre[x]} , pre[x] = mcnt ++;
        e[mcnt] = (edge) {x , pre[y]} , pre[y] = mcnt ++;
    }
    dfs(1 , 0);
    for (j = 1 ; 1 << j <= n ; ++ j)
        for (i = 1 ; i <= n ; ++ i)
            f[j][i] = f[j - 1][f[j - 1][i]];
    for (i = 1 ; i <= n ; ++ i)
        ADD(1 , n , L[i] , R[i] , a[i] , 1);
    for (i = 1 ; i <= m ; ++ i)
    {
        scanf("%d%d%d",&k,&x,&y);
        if (k)
        {
            sp = sm = 0 , z = LCA(x , y);
            QUERY(1 , n , L[x] , 1);
            QUERY(1 , n , L[y] , 1);
            QUERY(1 , n , L[z] , 0);
            if(f[0][z]) QUERY(1 , n , L[f[0][z]] , 0);
            k = query(Down , Up , k);
            if (~k) printf("%d\n" , k); else puts("invalid request!");
        }
        else
        {
            ADD(1 , n , L[x] , R[x] , a[x] , -1);
            a[x] = y;
            ADD(1 , n , L[x] , R[x] , a[x] , 1);
        }
    }
}

int main()
{
    work();
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值