虚树模板

虚树

所谓虚树,就是我们在做题中把用到的点建到一颗新树上。
比如如果我们当前询问一条链的两端,如果我们直接dfs(暴力)的话时间复杂度就是O(n)
但是如果我们用虚树,那么这棵树上就只会有这两个节点
两个节点之间的那条边记录了原本链的信息
于是复杂度从O(n)为了O(2)

一般题中除了给出的询问点,我们还会用到询问点的lca,但是lca数也是o(n^2)的,于是我们想到把关键点按dfs序排序,
比如设有三个点x,y,z,,他们按dfs序排序,lca(y,z)一定在lca(x,y)或者lca(y,z)中
(因为,一棵树上k个点的lca不管怎么组合应该是不超过k-1个的)。
于是我们现在找到了2*k级别的点,问题是怎么把他们丢到一棵树上去。

我们先再次把所有点按dfs序排序,然后我们考虑这样一种方法 : 我们维护一个栈,如果栈为空,直接加点,否则我们看栈顶的点是不是当前点的祖先:
如果不是,那么弹出栈顶继续,否则就可以把当前点加进栈了(然后从栈顶向这个点连一条边)。
(因为之前插入的点可能是和当前点是并列关系,这个不一定会需要用到lca,因为我们记录一个dfs时入队和出队的顺序就可以了)。(第一个点就直接作为根了)。

代码实现

#include <bits/stdc++.h>
#define max(x,y) (x>y?x:y)
#define min(x,y) (x<y?x:y)
#define inf 100000000ll
#define N 200010
using namespace std;
inline int read(){
    int ret=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())ret=ret*10+c-'0';
    return ret*f;
}
struct pppp{int to,nxt,len;}a[N*3],b[N*10];
int in[N],out[N],dep[N],f[N][25],he[N],pp=0;
int n,tim=0,m,root,fir[N];
int sum=0,siz=0,len,top,li[N],que[N];
long long Dp[N][3];
bool instack[N],mark[N],sta[N];
bool comp(const int &x,const int &y){return in[x]<in[y];}
bool check(int x,int y){return in[x]<=in[y]&&out[x]>=out[y];};
inline void add(int x,int y){
    a[++pp].to=y;a[pp].nxt=he[x];he[x]=pp;
    a[++pp].to=x;a[pp].nxt=he[y];he[y]=pp;
}
inline void inser(int x,int y,int z) {
    b[++siz].to=y;b[siz].nxt=fir[x];
    fir[x]=siz;b[siz].len=z;
}
void dfs(int x,int de,int fa){
    dep[x]=de;f[x][0]=fa;
    in[x]=++tim;
    for(int i=1;i<=20;++i)f[x][i]=f[f[x][i-1]][i-1];
    for(int i=he[x];i;i=a[i].nxt)
        if(dep[a[i].to]==0)dfs(a[i].to,de+1,x);
    out[x]=++tim;
}
int lca(int x,int y) {
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=20;i;--i) 
        if(dep[f[x][i]]>=dep[y])x=f[x][i];
    if(x==y)return x;
    for(int i=20;i;--i) 
        if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main(){int x,y,Q;
    n=read();
    for(int i=1;i<n;++i){
        x=read();y=read();
        add(x,y);
    }
    dfs(1,1,1);
    Q=read();
    while(Q--){
        m=read();len=m;top=0;root=0;
        for(int i=1;i<=m;++i)li[i]=read(),que[i]=li[i];
        sort(li+1,li+m+1,comp);
        for(int i=1;i<m;++i)li[++len]=lca(li[i],li[i+1]);
        sort(li+1,li+len+1,comp);
        len=unique(li+1,li+len+1)-li-1;
        for(int i=1;i<=len;++i){
            while(top>0&&!check(sta[top],li[i]))--top;
            if(sta[top]==0)root=li[i];
            else{
                int s=sta[top],t=li[i];
                inser(s,t,dep[t]-dep[s]);
            }
            sta[++top]=li[i];
        }
        for(int i=1;i<=len;++i)instack[li[i]]=true;
        for(int i=1;i<=m;++i)mark[que[i]]=true;
        for(int i=1;i<=len;++i)instack[li[i]]=false;
        for(int i=1;i<=m;++i)mark[que[i]]=false;
        for(int i=1;i<=len;++i)fir[li[i]]=0;siz=0;
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值