• 【知识点】虚树


    简介:

    (听名字高大上,实际上没什么东西……虚树的题主要难在如何操作虚树)

    给出$k$个关键点,我们要建出一棵只包含这些关键点和他们$lca$的点数最少的树,以实现$dp$等操作。

    标志性的数据范围是$sum{k}leq 10^{5}$之类的。

    建树方法:

    1.将所有关键点按$dfs$序排序。

    2.开一个栈表示根到当前点的虚树路径,并把根丢进去。

    3.对于每一个关键点$u$:令$lca$为$u$与$s[top]$的最近公共祖先。

    若$lca=s[top]$,则把$u$丢进去。

    否则,我们需要弹出栈中所有不在根到$u$路径上的点。

    由于关键点是按照$dfs$序排序的,那么栈中所有$dep$大于$dep[lca]$的点都不在这条路径上。(容易验证它们一定在$lca$的另外一颗子树上)

    于是一直弹到最后一个不满足要求的点,每弹出一个点之后就在它和栈顶间连一条边。

    由于我们要把$lca$丢进栈里,所以最后一个点应当与$lca$连边,然后弹掉。

    最后把$lca$和$u$丢进栈里。(如果$s[top]=lca$就不用丢$lca$进去了)

    4.最后把栈里所有点弹掉,每弹出一个点之后就在它和栈顶间连一条边。

    复杂度分析:

    每加一个点最多产生一个$lca$,那么虚树中的总点数是$O(2k)$的。

    清空数组的时候千万要计算好复杂度。

    代码(CF613D Kingdom and its Cities):

    #include<bits/stdc++.h>
    #define maxn 200005
    #define maxm 500005
    #define inf 0x7fffffff
    #define ll long long
    #define debug(x) cerr<<#x<<": "<<x<<endl
    #define fgx cerr<<"--------------"<<endl
    #define dgx cerr<<"=============="<<endl
    
    using namespace std;
    int f[maxn][20],dep[maxn],dfn[maxn],tot;
    int hd[maxn],to[maxn<<1],nxt[maxn<<1],cnt;
    int st[maxn],po[maxn],vis[maxn],ans,flag; 
    vector<int> vc[maxn];
    
    inline int read(){
        int x=0,f=1; char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x*f;
    }
    
    inline bool cmp(int a,int b){return dfn[a]<dfn[b];}
    inline void add(int a,int b){vc[a].push_back(b);}
    
    inline void addedge(int u,int v){
        to[++cnt]=v,nxt[cnt]=hd[u],hd[u]=cnt;
        to[++cnt]=u,nxt[cnt]=hd[v],hd[v]=cnt;
    }
    
    inline void dfs(int u,int fa){
        dfn[u]=++tot,dep[u]=dep[fa]+1,f[u][0]=fa;
        for(int i=1;i<20;i++) f[u][i]=f[f[u][i-1]][i-1];
        for(int i=hd[u];i;i=nxt[i]) if(to[i]!=fa) dfs(to[i],u);
    }
    
    inline int lca(int u,int v){
        if(dep[u]<dep[v]) swap(u,v);
        for(int i=19;i>=0;i--)
            if(dep[f[u][i]]>=dep[v]) u=f[u][i];
        if(u==v) return u;
        for(int i=19;i>=0;i--)
            if(f[u][i]!=f[v][i]) 
                u=f[u][i],v=f[v][i];
        return f[u][0];
    }
    
    inline int solve(int u){
        int num=0;
        for(int i=0;i<vc[u].size();i++){
            int v=vc[u][i]; num+=solve(v);
            if(vis[v]&&vis[u]&&dep[v]==dep[u]+1) flag=1;
        }
        vc[u].clear();
        if(vis[u]){ans+=num;return 1;}
        else{
            if(num<=1) return num;
            else {ans++;return 0;}
        }
    }
    
    int main(){
        int n=read();
        for(int i=1;i<=n-1;i++)
            addedge(read(),read());
        dfs(1,0);
        int Q=read();
        while(Q--){
            int k=read(); ans=0,flag=0;
            for(int i=1;i<=k;i++) po[i]=read(),vis[po[i]]=1;
            sort(po+1,po+1+k,cmp);
            st[0]=0,st[++st[0]]=1;
            for(int i=1;i<=k;i++){
                if(st[0]==1){if(po[i]!=1)st[++st[0]]=po[i];continue;}
                int lt=lca(po[i],st[st[0]]);
                while(st[0]>1 && dep[lt]<dep[st[st[0]-1]])
                    add(st[st[0]-1],st[st[0]]),st[0]--;
                if(dep[lt]<dep[st[st[0]]]) add(lt,st[st[0]]),st[0]--;
                if(st[st[0]]!=lt) st[++st[0]]=lt; st[++st[0]]=po[i];
            }
            while(st[0]>1) add(st[st[0]-1],st[st[0]]),st[0]--;
            solve(1),printf("%d
    ",(flag)?-1:ans);
            for(int i=1;i<=k;i++) vis[po[i]]=0;
        }
        return 0;
    }
    虚树
  • 相关阅读:
    Linux_vi编辑器
    Linux_几个符号命令
    Linux_权限
    Linux_用户/用户组
    Linux_文件及文件夹[创建][复制][移动][删除][重命名]
    Linux_文件查看
    Linux_初识
    码农网站
    学习网站
    软件设计师考试范围
  • 原文地址:https://www.cnblogs.com/YSFAC/p/12037564.html
Copyright © 2020-2023  润新知