牛客多校第七场-F-dfs序,区间修改主席树

这篇博客介绍了如何利用数据结构中的主席树来解决一道算法题,该题要求在两棵树中找到一条最长的竖直联通链,在第二棵树中链上的点两两不为子树关系。博主首先分析了问题的性质,指出子树关系可以转化为DFS序上的区间关系,并转换为寻找区间不相交的最长链。接着,博主详细阐述了两种实现主席树的方法:懒标记+pushdown和标记永久化,并提供了对应的C++代码实现。通过主席树,博主成功地解决了问题并给出了最长链的长度。
题目大意:

经过转换,题意变成,给你两颗树.在第一颗树上找到一条竖着的联通链(任意两个点的lca为其中的一个点).使得这些点在第二颗树上两两不为子树关系.(即任意两个点的lca不为其中的一个).问你最长链长度。

题目思路:

发现性质: 子树关系可以转换成dfs序上的区间关系
所以转换成,第一棵树上每个点都有一个区间[Li,Ri][L_i,R_i][Li,Ri].
问你最长链使得节点的区间两两不相交.

做法是:dfs的时候维护能够向上走多少个节点使得两两区间不相交。令这个变量为la[i]la[i]la[i].然后巧妙的转移它:

实现方法是,线段树维护区间最大值.对于iii点,查询区间[Li,Ri][L_i,R_i][Li,Ri]里出现的点的最大深度mxmxmx.
因为对于树,dfs的时候深度可以代表出现的时间.出现的时间越晚,深度越大.
然后用dep[i]dep[i]dep[i]覆盖[Li,Ri][L_i,R_i][Li,Ri].

然后转移:la[i]=min(la[father[i]]+1,dep[i]−mx)la[i] = min(la[father[i]] + 1 , dep[i] - mx)la[i]=min(la[father[i]]+1,dep[i]mx)

关于线段树,可以使用带区间修改的主席树维护,也可以直接使用线段树+dfs回溯时撤销贡献的方法,这里笔者为了熟练使用主席树,选择第一种方法。

带区间修改的主席树:

方法一:懒标记+pushdown

具体做法: addaddadd的时候,注意两点改变
1.若节点区间完全被查询区间覆盖,则打标记,然后将左右儿子置为空
2.否则,要pushdown:注意推左右儿子的时候,当不存在左or右儿子的时候才开点.

askaskask的时候,记得pushdown即可。


方法二:标记永久化
不难发现,上面那种方法非常容易炸空间。在本题条件下,我们可以使用标记永久化的技巧来做这题

但注意,这题每次覆盖的值一定比之前的大,所以才能标记永久化,不然无法做。

代码仓库

方法一

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define fi first
#define se second
#define mid ((l + r) >> 1)
const int maxn = 3e5 + 5;
const int maxm = 3e5 * 19 * 19 + 5;
const int mod = 1e9 + 7;
vector<int> e[2][maxn];
int n , L[maxn] , R[maxn] , dep[maxn] , tot;
void dfs1 (int u , int fa){
    L[u] = ++tot;
    for (auto v : e[1][u]){
        if (fa == v) continue;
        dfs1(v , u);
    }
    R[u] = tot;
    return ;
}
// 主席树
int rt[maxn] , ls[maxm] , rs[maxm] , lz[maxm] , mx[maxm] , cnt;
void pushdown (int t){
    if (!lz[t]) return ;
    if (!ls[t]) ls[t] = ++cnt;
    if (!rs[t]) rs[t] = ++cnt;
    lz[ls[t]] = lz[rs[t]] = lz[t];
    mx[ls[t]] = mx[rs[t]] = lz[t];
    lz[t] = 0;
}
void pushup(int t)
{
    mx[t] = max(mx[ls[t]] , mx[rs[t]]);
}
void add (int & t , int fa , int l , int r , int L , int R , int v){
    // 先开个新点,继承之前的点
    t = ++cnt;
    ls[t] = ls[fa];
    rs[t] = rs[fa];
    mx[t] = mx[fa];
    lz[t] = lz[fa];
    // 正常线段树
    if (L <= l && r <= R){
        mx[t] = lz[t] = v;
        ls[t] = rs[t] = 0;
        return ;
    }
    // pushdown 之前那棵树
    pushdown(fa);
    if (L <= mid) add(ls[t] , ls[fa] , l , mid , L , R , v);
    if (R > mid) add(rs[t] , rs[fa] , mid + 1 , r , L , R , v);
    // pushup 现在这棵树
    pushup(t);
}
int ask (int t , int l , int r , int L , int R){
    if (L <= l && r <= R){
        return mx[t];
    }
    pushdown(t);
    int ans = 0;
    if (L <= mid) ans = max(ans , ask(ls[t] , l , mid , L , R));
    if (R > mid) ans = max(ans , ask(rs[t] , mid + 1, r , L , R));
    return ans;
}
int ans = 0;
void dfs2 (int u , int fa , int la){
    dep[u] = dep[fa] + 1;
    int mx = ask(rt[fa] , 1 , n , L[u] , R[u]);
    int nla = min(la + 1 , dep[u] - mx);
    ans = max(ans , nla);
    add(rt[u] , rt[fa] , 1 , n , L[u] , R[u] , dep[u]);
    for (auto v : e[0][u]){
        if (fa == v) continue;
        dfs2(v , u , nla);
    }
    return ;
}
void init(){
    cnt = ans = tot = 0;
    for (int i = 1 ; i <= n ; i++){
        rt[i] = 0;
        e[0][i].clear();
        e[1][i].clear();
    }
}
int main()
{
    int t; scanf("%d" , &t);
    while (t--){
        scanf("%d" , &n);
        init();
        for (int i = 1 ; i < n ; i++){
            int x , y; scanf("%d%d" , &x , &y);
            e[0][x].pb(y);
            e[0][y].pb(x);
        }
        for (int i = 1 ; i < n ; i++){
            int x , y; scanf("%d%d" , &x , &y);
            e[1][x].pb(y);
            e[1][y].pb(x);
        }
        dfs1 (1 , 0);
        dfs2 (1 , 0 , 0);
        printf("%d\n" , ans);
        //
        for (int i = 1 ; i <= cnt ; i++)
            ls[i] = rs[i] = mx[i] = lz[i] = 0;
    }
    return 0;
}

方法二:(只放不一样的部分)

// 主席树
int rt[maxn] , ls[maxm] , rs[maxm] , lz[maxm] , mx[maxm] , cnt;
void add (int & t , int fa , int l , int r , int L , int R , int v){
    // 先开个新点,继承之前的点
    t = ++cnt;
    ls[t] = ls[fa];
    rs[t] = rs[fa];
    mx[t] = v;
    lz[t] = lz[fa];
    // 正常线段树
    if (L == l && r == R){
        lz[t] = v;
        ls[t] = rs[t] = 0;
        return ;
    }
    if (R <= mid) add(ls[t] , ls[fa] , l , mid , L , R , v);
    else if (L > mid) add(rs[t] , rs[fa] , mid + 1 , r , L , R , v);
    else add(ls[t] , ls[fa] , l , mid , L , mid , v) ,
            add(rs[t] , rs[fa] , mid + 1 , r , mid + 1 , R , v);
}
int ask (int t , int l , int r , int L , int R , int res){
    if (!t) return res;
    if (L == l && r == R){
        return max(mx[t] , res);
    }
    res = max(lz[t] , res);
    if (R <= mid) return ask(ls[t] , l , mid , L , R , res);
    else if (L > mid) return ask(rs[t] , mid + 1, r , L , R , res);
    return max(ask(ls[t] , l , mid , L , mid , res) ,
               ask(rs[t] , mid + 1, r , mid + 1 , R , res));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值