【知识点】虚树
简介:
(听名字高大上,实际上没什么东西……虚树的题主要难在如何操作虚树)
给出$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):
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }