题目大意
链接
给定一棵树,每次给定一些点,求最少要阻断多少点使得这些点之间两两之间无法通行。其中不能阻断给定点,无解输出-1.
分析
虚树上贪心/DP即可。
虚树
由于在树上有可能有很多点根本就不需要用,所以我们要把这些点提出来单独组成一棵树,减少了大量运算。
一般来说我们要提出的点是所有的点和这些点之间两两LCA的点,然而K个点LCA的点仅仅有K-1个,也就是按照DFS序排序之后相邻的点之间LCA即可。
我挑了一种比较好理解和易于自己实现的写法。
首先获得原树,然后遍历之后得到DFS序,并且拥有一个办法求LCA,求出DFS序,求出每个点所在子树的时间戳范围。
对于每一组数据我们有如下操作:
- 获取结点,按照DFS序排序,相邻结点之间获取它们的LCA,再把所有结点按照DFS序排序,去重。
- 弄一个栈,先加入DFS序最小的,它也就是整个虚树的根。
- 依次加入结点,如果当前结点不在栈顶结点的子树内,就弹出栈顶结点。然后连接栈顶元素和当前元素,把当前元素入栈。
好了虚树就构建好了。
说几个注意的点:
- 建立虚树的时候是个有根树,可以不向根的方向连边,因此遍历的时候不用判断父亲结点。
- 边和其它数据的还原最好利用我们得到的结点逐个还原,因为原树太大了。
- 注意排序的时候要加cmp函数,但是unique的时候不要加。
代码
#include<cmath>
#include<queue>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=3e5+105,inf=1e6,oo=18;
int n,m;
int dfn,l[maxn],r[maxn],dep[maxn],fa[maxn][oo+5];
struct Tree1
{
int np,first[maxn];
struct edge{
int to,next;
}E[maxn<<1];
void add(int u,int v)
{
E[++np]=(edge){v,first[u]};
first[u]=np;
}
void DFS(int i,int f,int d)
{
l[i]=++dfn;
dep[i]=d;
fa[i][0]=f;
for(int j=1;j<=oo;j++)fa[i][j]=fa[fa[i][j-1]][j-1];
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
if(j==f)continue;
DFS(j,i,d+1);
}
r[i]=dfn;
}
int LCA(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
int delt=(dep[u]-dep[v]);
for(int j=0;j<=oo;j++)
if((delt>>j)&1)u=fa[u][j];
if(u==v)return u;
for(int j=oo;j>=0;j--)
if(fa[u][j]!=fa[v][j])u=fa[u][j],v=fa[v][j];
return fa[u][0];
}
void Init()
{
int u,v;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
DFS(1,0,1);
scanf("%d\n",&m);
}
}tr;
int cnt,np,first[maxn],d[maxn],stk[maxn],top;
bool vis[maxn];
int sz[maxn],f[maxn];
struct edge{
int to,next;
}E[maxn<<1];
void add(int u,int v)
{
E[++np]=(edge){v,first[u]};
first[u]=np;
}
bool cmp(int x,int y)
{
return l[x]<l[y];
}
void Build()
{
sort(d+1,d+cnt+1,cmp);
int to=cnt;
for(int i=1;i<to;i++)
d[++cnt]=tr.LCA(d[i],d[i+1]);
sort(d+1,d+cnt+1,cmp);
cnt=unique(d+1,d+cnt+1)-d-1;
top=0;
stk[++top]=d[1];
for(int i=2;i<=cnt;i++)
{
while(top && r[stk[top]]<l[d[i]])top--;
if(top)
add(stk[top],d[i]);
stk[++top]=d[i];
}
}
void dp(int i)
{
f[i]=0;
if(vis[i])
{
sz[i]=1;
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
dp(j);
f[i]+=f[j];
if(sz[j])f[i]++;
}
}
else
{
sz[i]=0;
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
dp(j);
f[i]+=f[j];
sz[i]+=sz[j];
}
if(sz[i]>1)
{
sz[i]=0;
f[i]++;
}
}
}
void query()
{
bool ok=false;
scanf("%d",&cnt);
for(int i=1;i<=cnt;i++)
{
scanf("%d",&d[i]);
vis[d[i]]=1;
}
for(int i=1;i<=cnt;i++)
if(vis[fa[d[i]][0]])ok=true;
if(ok)
printf("-1\n");
else
{
Build();
dp(d[1]);
printf("%d\n",f[d[1]]);
}
np=0;
for(int i=1;i<=cnt;i++)
{
vis[d[i]]=0;
first[d[i]]=0;
}
}
int main()
{
tr.Init();
while(m--)
query();
return 0;
}