牛客多校7 F xay loves trees

题目链接

题意:给定两颗以1为根n个点的树,问你最大的集合S,满足S中的任意两个元素在第一颗树上必有一个点是另一个点的祖先,且要是连续的一些点,在第二颗树上都不是对方的祖先。

1<=n<=3e5

第一个条件可以看出来是一条链,对于第二个条件如果选了一个点,那么其子树一定不能选。所以我们可以dfs序处理一下,则选了一个点u我们就将其在线段树上 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]]覆盖,如果有重复覆盖的那么一定是两者只能取其一,可以遍历第一颗树然后用线段树来维护,线段树里面存遍历路径上的深度最大值,那么答案 a n s = m a x ( a n s , d [ u ] − d i s ) ans=max(ans,d[u]-dis) ans=max(ans,d[u]dis),dis为到当前结点且有重复覆盖的最大深度。
因为线段树上面有覆盖且有回溯,所以除了可持久化还可以建立vector来维护线段树,vector里面存当前区间所有的深度,如果这段区间被覆盖就 p u s h _ b a c k push\_back push_back进去,否则就 p o p _ b a c k pop\_back pop_back出来。因为是取max所以可以不用写pushdown,直接在查询的时候取个max即可。
很多题解是用双指针,如果上面的点与该点矛盾,则在线段树上修改这个值,不断将上面的点往下移,然后在回溯的时候再一个一个往上跳修改,这个复杂度其实是O( n 2 l o g n n^2logn n2logn)的,因为第一颗树如果是n/2个点都在一条链上,然后n/2个点以这个链的末尾为父亲结点,这样就会被卡掉,每个点的更新都会移动2*n/2次,所以总共移动 n ∗ n / 2 n*n/2 nn/2次。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e5+10;
vector<int>G1[maxn],G2[maxn];
int k,in[maxn],out[maxn];
void dfs(int u,int fa)
{
    in[u]=++k;
    for(auto v:G2[u])
    {
        if(v==fa) continue;
        dfs(v,u);
    }
    out[u]=k;
}
vector<int>tree[maxn*4];
int d[maxn],mx[maxn*4];
void pushup(int l,int r,int x)
{
    mx[x]=0;
    if(tree[x].size()) mx[x]=tree[x].back();    //可能子节点的深度比x更大 这里取max没有用pushdown
    if(l==r) return ; //根节点
    mx[x]=max(mx[x],mx[x<<1]);
    mx[x]=max(mx[x],mx[x<<1|1]);
}
void modify(int l,int r,int x,int st,int ed,int c)             //维护max
{
    if(st<=l&&r<=ed) {
        if(c>0) tree[x].push_back(d[c]);
        else tree[x].pop_back();
        pushup(l,r,x);
        return ;
    }
    int mid=l+r>>1;
    if(mid>=st) modify(l,mid,x<<1,st,ed,c);
    if(mid<ed) modify(mid+1,r,x<<1|1,st,ed,c);
    pushup(l,r,x);
}
int query(int l,int r,int x,int st,int ed)    //询问[st,ed]的最大深度
{
    if(st<=l&&r<=ed) return mx[x];
    int mid=l+r>>1;
    int ans=0;
    if(tree[x].size()) ans=tree[x].back();    //没有用pushdown
    if(mid>=st) ans=max(ans,query(l,mid,x<<1,st,ed));
    if(mid<ed) ans=max(ans,query(mid+1,r,x<<1|1,st,ed));
    return ans;
}
int ans=1;
void dfs1(int u,int fa,int dis)                     //dis表示上一结点矛盾最大深度
{
    dis=max(dis,query(1,k,1,in[u],out[u]));         //与当前结点矛盾最大深度
    ans=max(ans,d[u]-dis);
    modify(1,k,1,in[u],out[u],u);
    for(auto v:G1[u])
    {
        if(v==fa) continue;
        d[v]=d[u]+1;
        dfs1(v,u,dis);
    }
    modify(1,k,1,in[u],out[u],-1);
}
int main() 
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;cin>>t;
    while(t--)
    {
        int n;cin>>n;
        for(int i=1;i<=n;i++)
        G1[i].clear(),G2[i].clear();
        for(int i=1;i<n;i++)
        {
            int x,y;cin>>x>>y;
            G1[x].push_back(y);
            G1[y].push_back(x);
        }
        for(int i=1;i<n;i++)
        {
            int x,y;cin>>x>>y;
            G2[x].push_back(y);
            G2[y].push_back(x);
        }
        k=0;dfs(1,1);
        ans=1;d[1]=1;
        for(int i=1;i<=4*k;i++)
        tree[i].clear(),mx[i]=0;
        dfs1(1,1,0);
        cout<<ans<<endl;
    }
 	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值