HDU 4718 The LCIS on the Tree(树链剖分+线段树)

本文介绍了一种使用树链剖分解决树上两点间最长连续递增子序列问题的方法。通过树链剖分将树分解为轻链和重链,并在每条重链上建立线段树来高效处理查询。文章详细解释了实现过程,包括如何处理递增与递减情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:The LCIS on the Tree

题意:给定一棵树,然后多个询问,a, b,将树上a到b的最短路径上,结点的权值按顺序写下来,求出里面最长的单调递增连续子序列的长度。

先说这道题有个简化的版本,用线段树解决:LCIS

对于LCIS这题,在合并左右区间时如果左区间的右端点的值v1,小于右区间的左端点的值v2,那么可以把它们这两边连接起来,更新答案,所以线段树的结点要附加上结点左右端点的值,连接两个端点的最长序列的长度,以及本节点的最优值,然后不断合并。代码就不贴了,建议先做这个,再来搞本题。

说回本题,要处理的问题从一条线段变成一棵树,所以采用树链剖分来做。

树链剖分其实就是将树划分成轻链和重链,每条链就是一个线性的区间,然后分而治之,然后合并起来。

可以参考这篇文章:http://blog.sina.com.cn/s/blog_6974c8b20100zc61.html

自己把代码写一遍应该就会有所理解了。

首先是常规的树链剖分,对每条重链建线段树。

跟LCIS不同的是,我们建线段树的时候都是从底向上增大结点编号的,但有时候我们的路径是反过来向下走的,所以除了原先单调递增的情况,我们还要记录单调递减的信息。

于是你们就会看到下面代码里面函数带有超多参数。。。。

主要思想是,如果是起点方向向上走,那么找递增,反之找递减。最后再将递增递减两条链合并(如果可以合并的话),答案就来了。。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int N = 100010;
#define pb push_back
#define CLR(o) memset(o, 0, sizeof(o))
#define lson tr[o].lch
#define rson tr[o].rch
#define lchd tr[tr[o].lch]
#define rchd tr[tr[o].rch]
#define cur tr[o]
vector<int> V[N];
struct TreeNode{
    int l, r, inc, des, lv, rv, illen, irlen, dllen, drlen;
    int lch, rch;
}tr[N*10];
int T, n, path_cnt, node_cnt;
int v[N];//结点权值
int father[N];//每个节点的父节点
int size[N];//每个节点的子节点数,包括自身
int belong[N];//结点所属的线段树编号
int rank[N];//每个节点在所属线段树的编号
int path_dep[N];//当前重链深度最小的结点的深度
int path_top[N];//重链深度最小的结点编号
int path_size[N];//重链长度
int tree[N];//每条重链对应的线段树根节点编号
map<int, map<int,int> > MP;//用于查找线段树上结点的权值
/*以下是线段树部分代码*/
void maintain(int o){
    cur.inc = max(lchd.inc, rchd.inc);
    cur.des = max(lchd.des, rchd.des);
    cur.lv = lchd.lv; cur.illen = lchd.illen; cur.dllen = lchd.dllen;
    cur.rv = rchd.rv; cur.irlen = rchd.irlen; cur.drlen = rchd.drlen;
    int left = lchd.r - lchd.l + 1;
    int right = rchd.r - rchd.l + 1;
    if(lchd.rv < rchd.lv){
        //左孩子的右值小于右孩子的左值
        int len = lchd.irlen + rchd.illen;
        cur.inc = max(cur.inc, len);
        if(lchd.irlen == left){
            cur.illen = len;
        }
        if(rchd.illen == right){
            cur.irlen = len;
        }
    }
    else if(lchd.rv > rchd.lv){
        //左孩子的右值大于右孩子的左值
        int len = lchd.drlen + rchd.dllen;
        cur.des = max(cur.des, len);
        if(lchd.drlen == left){
            cur.dllen = len;
        }
        if(rchd.dllen == right){
            cur.drlen = len;
        }
    }
}
void build(int path_id, int o, int ll, int rr){
    cur.l = ll;
    cur.r = rr;
    if(ll<rr){
        int m = (ll+rr)>>1;
        lson = ++node_cnt;
        build(path_id, lson, ll, m);
        rson = ++node_cnt;
        build(path_id, rson, m+1, rr);
        maintain(o);
    }
    else{
        cur.inc = cur.des = 1;
        cur.lv = cur.rv = MP[path_id][ll];
        cur.illen = cur.irlen = cur.dllen = cur.drlen = 1;
    }
}
/*
区间合并操作,返回该区间的最优值
f为1是表示递增,0表示递减,以下其他函数相同
left,right分别是左右区间的长度
rv:左区间的右端点的值
L:左区间连接左端点的最大值
R:左区间连接右端点的最大值
后三个参数类似
*/
int merge(bool f, int left, int right, int rv, int &L, int &R, int lv1, int L1, int R1){
    int len = R+L1;
    int M = 0;
    if(f){
        if(rv < lv1){
            M = max(M, len);
            if(R == left)   L = len;
            if(L1 == right) R = len;
            else    R = R1;
        }
        else    R = R1;
    }
    else{
        if(rv > lv1){
            M = max(M, len);
            if(R == left)   L = len;
            if(L1 == right) R = len;
            else    R = R1;
        }
        else    R = R1;
    }
    return M;
}
/*
查询函数,返回当前查询区间的最优值
lv,rv:查询的区间的左右端点的值
L, R:查询的区间的左右端点连接的最长长度
*/
int query(int o, int ll, int rr, bool f, int &lv, int &rv, int &L, int &R){
    if(cur.l == ll && cur.r == rr){
        lv = cur.lv; rv = cur.rv;
        if(f){
            L = cur.illen; R = cur.irlen;
            return cur.inc;
        }
        else{
            L = cur.dllen; R = cur.drlen;
            return cur.des;
        }
    }
    int m = (cur.l + cur.r)>>1;
    if(rr<=m)   return query(lson, ll, rr, f, lv, rv, L, R);
    else if(ll>m)   return query(rson, ll, rr, f, lv, rv, L, R);
    else{
        int lv1, rv1, L1, R1;
        int m1 = query(lson, ll, m, f, lv, rv, L, R);
        int m2 = query(rson, m+1, rr, f, lv1, rv1, L1, R1);
        int M = max(m1, m2);
        M = max(M, merge(f, m-ll+1, rr-m, rv, L, R, lv1, L1, R1));
        rv = rv1;
        return M;
    }
}
/*线段树部分代码结束*/
/*dfs完成树链剖分*/
void dfs(int x, int dep){
    size[x] = 1;
    int key=-1;
    int M = 0;
    for(int i=0; i<V[x].size(); i++){
        int j = V[x][i];
        father[j] = x;
        dfs(j, dep+1);
        size[x] += size[j];
        if(size[j]>M){
            M = size[j];
            key = i;
        }
    }
    belong[x] = 0;
    for(int i=0; i<V[x].size(); i++){
        int j = V[x][i];
        if(i==key){
            belong[x] = belong[j];
            rank[x] = rank[j]+1;
        }
        else{
            int u = belong[j];
            path_size[u] = rank[j];
            path_dep[u] = dep;
            path_top[u] = j;
        }
    }
    if(!belong[x]){
        belong[x] = ++path_cnt;
        rank[x] = 1;
    }
    MP[belong[x]][rank[x]] = v[x];
}
void init(){
    MP.clear();
    father[1] = 0;
    path_cnt = node_cnt = 0;
    dfs(1, 1);
    int u = belong[1];
    path_dep[u] = 0;
    path_size[u] = rank[1];
    path_top[u] = 1;
    for(int i=1; i<=path_cnt; i++){
        tree[i] = ++node_cnt;
        build(i, tree[i], 1, path_size[i]);
    }
}
int Q(int a, int b){
    int M = 0;
    int x = belong[a];
    int y = belong[b];
    int lv1, rv1, L1, R1, llen;
    int lv2, rv2, L2, R2, rlen;
    bool f1=0, f2=0;
    int lv, rv, L, R;
    //当a和b不在同一条链时
    while(x!=y){
        if(path_dep[x] > path_dep[y]){
            if(f1){
                M = max(M, query(tree[x], rank[a], path_size[x], 1, lv, rv, L, R));
                M = max(M, merge(1, llen, path_size[x]-rank[a]+1, rv1, L1, R1, lv, L, R));
                rv1 = rv;
                llen += path_size[x]-rank[a]+1;
            }
            else{
                M = max(M, query(tree[x], rank[a], path_size[x], 1, lv1, rv1, L1, R1));
                llen = path_size[x]-rank[a]+1;
                f1 = 1;
            }
            a = father[path_top[x]];
            x = belong[a];
        }
        else{
            if(f2){
                M = max(M, query(tree[y], rank[b], path_size[y], 0, lv, rv, L, R));
                M = max(M, merge(0, rlen, path_size[y]-rank[b]+1, rv2, L2, R2, lv, L, R));
                rv2 = rv;
                rlen += path_size[y]-rank[b]+1;
            }
            else{
                M = max(M, query(tree[y], rank[b], path_size[y], 0, lv2, rv2, L2, R2));
                rlen += path_size[y]-rank[b]+1;
                f2 = 1;
            }
            b = father[path_top[y]];
            y = belong[b];
        }
    }
    //当a和b在同一条链时
        if(rank[a]>rank[b]){
            if(f2){
                M = max(M, query(tree[x], rank[b], rank[a], 0, lv, rv, L, R));
                M = max(M, merge(0, rlen, rank[a]-rank[b]+1, rv2, L2, R2, lv, L, R));
                rv2 = rv;
                rlen += rank[a]-rank[b]+1;
            }
            else{
                M = max(M, query(tree[x], rank[b], rank[a], 0, lv2, rv2, L2, R2));
                rlen = rank[a]-rank[b]+1;
                f2 = 1;
            }
        }
        else{
            if(f1){
                M = max(M, query(tree[x], rank[a], rank[b], 1, lv, rv, L, R));
                M = max(M, merge(1, llen, rank[b]-rank[a]+1, rv1, L1, R1, lv, L, R));
                rv1 = rv;
                llen += rank[b]-rank[a]+1;
            }
            else{
                M = max(M, query(tree[x], rank[a], rank[b], 1, lv1, rv1, L1, R1));
                llen = rank[b]-rank[a]+1;
                f1 = 1;
            }
        }
    //合并递增和递减两条链,注意之前都是递增和递增合并,递减和递减合并
    if(f1 && f2 && rv1 < rv2){
        M = max(M, R1+R2);
    }
    return M;
}
int main(){
    scanf("%d", &T);
    for(int t=1; t<=T; t++){
        if(t>1) puts("");
        scanf("%d", &n);
        for(int i=1; i<=n; i++){
            V[i].clear();
            scanf("%d", v+i);
        }
        int a, b, q;
        for(int i=2; i<=n; i++){
            scanf("%d", &a);
            V[a].pb(i);
        }
        init();
        scanf("%d", &q);
        printf("Case #%d:\n", t);
        while(q--){
            scanf("%d %d", &a, &b);
            printf("%d\n", Q(a, b));
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值