【模板】虚树

核心思想:

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

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

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

建树方法:

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

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

3.对于每一个关键点$u$:

若栈中只有根这一个元素,则把$u$丢进去。

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

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

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

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

然后把$lca$和$u$丢进栈里。容易验证此时栈中的点一定形成一条根到$u$的路径。

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\n",(flag)?-1:ans);
        for(int i=1;i<=k;i++) vis[po[i]]=0;
    }
    return 0;
}

虚树

原文地址:https://www.cnblogs.com/YSFAC/p/12037564.html

时间: 2024-10-10 01:54:19

【模板】虚树的相关文章

[模板] 虚树

目的 解决树上多组关于某些点的询问,复杂度是关于每次询问点的个数的和的 思路 把每次询问的点和某些lca(即关键点)浓缩到虚树上,两点之间的连边包含原树中两点间路径的信息,再在虚树上暴力(?)处理 做法 先按照dfs序排序,这样可以保证做到不在某点子树中的点时,它的子树中的点都已经做完了 用一个栈来记录从虚树根到当前做到的点的链.虽然树根是谁都行,但方便起见直接给成1,并把1和第一个点压入栈中 现在新加入一个点p,设栈顶元素是x,p和x的最近公共祖先是lca 有两种情况(lca绝对不会等于p来着

虚树初探

虚树其实没什么的.. 只是因为点太多了不能全开于是只开那些需要用到的点. 一棵虚树包括要求点以及它们的lca.. 虚树的构建...(其实感觉如果会虚树的构建的话接下来就是树dp啦没什么的... 首先我们应该对整棵树dfs,求出它的dfs序列.然后对于给的点,按dfs排序.. 因为我们是按dfs序排列的,所以虚树一定是由一条条链构成的.. 扫一遍给的点,如果这个点在当前的这条链上,那加在栈顶就可以了. 如果不是的话,那就不断地退栈使的原来的那条链上面的边全部被加到边集中.. rep(i,1,n){

bzoj 2286 [Sdoi2011]消耗战(虚树+树上DP)

2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1276  Solved: 445[Submit][Status][Discuss] Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸

bzoj 3572 [Hnoi2014]世界树(虚树+DP)

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 645  Solved: 362[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.     世界树的形态可以用一个数学模型来描述:世界树中有n个

luogu3384 【模板】树链剖分

P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数N.M.R.P,

P3374 【模板】树状数组 1

P3374 [模板]树状数组 1 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x k 含义:将第x个数加上k 操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和 输出格式: 输出包

luogu P3368 【模板】树状数组 2

P3368 [模板]树状数组 2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数数加上x 2.求出某一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含2或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k 操作2: 格式:2 x 含义:输出第x个数的值 输出格式: 输出

BZOJ 3572: [Hnoi2014]世界树 [虚树 DP 倍增]

传送门 题意: 一棵树,多次询问,给出$m$个点,求有几个点到给定点最近 写了一晚上... 当然要建虚树了,但是怎么$DP$啊 大爷题解传送门 我们先求出到虚树上某个点最近的关键点 然后枚举所有的边$(f,x)$,讨论一下边上的点的子树应该靠谁更近 倍增求出分界点 注意有些没出现在虚树上的子树 注意讨论的时候只讨论链上的不包括端点,否则$f$的子树会被贡献多次 学到的一些$trick:$ 1.$pair$的妙用 2.不需要建出虚树只要求虚树的$dfs$序(拓扑序)和$fa$就可以$DP$了 注意

洛谷P3374 【模板】树状数组 1

P3374 [模板]树状数组 1 140通过 232提交 题目提供者HansBug 标签 难度普及/提高- 提交  讨论  题解 最新讨论 题目描述有误 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操作1: 格式: