题意:
多组数据,给你一张n个点m条边的无向图,保证连通,多组询问,每次询问选出若干个点,问你在图中有多少个没有被选中的点能在删去之后使得至少有一对选中的点不再连通。
n
,
m
,
∑
n,m,\sum
n,m,∑选出的点数都是
2
e
5
2e5
2e5量级的。
题解:
去年SDOI二轮Day1的T2,本弱当场爆零,记得当时企图当场yy把图变成树的方法,然后树剖,就可以做subtask2的45分,但是写了很久也写不出来。而当时zyb大爷当场A掉了,stl也差点A掉。于是半年后再来做这道题,感觉不是那么不可做了,毕竟同机房的大爷们早在5月就A了(我的水平已经落后他们半年了)。
早就知道了这题的套路是圆方树上建虚树,于是为了做这道题我之前就在学圆方树和虚树。这道题其实相当于问两点间的割点数的样子,所以建出圆方树之后两点间的每一个圆点都是合法的,于是我们只需要统计圆点的个数。我们在dfs预处理的时候先算出每个点到根的路径上(包括自己)经过了多少个圆点,然后我们对于每次询问,建出虚树。虚树上的边权是两个点在原树上之间的圆点个数的差,这样我们甚至不用具体地连出虚树的边,只要在建树的过程中就可以计算出整棵虚树有多少个圆点了。计算出来之后我们用圆点总数减去我们选出的圆点个数就是答案了。我们计算的方法是建虚树的栈存的是一条链,于是只需要在插入当前点的时候处理出是和哪个点连,于是用两个到根的圆点数减一下就好了。这个题对于虚树的根,我是找了所有选出的点的LCA,这样可能常数略大,感觉可能有更好的办法,但是这样至少肯定不会错。另外我们特判一下根节点是不是圆点,是的话要把答案加个1。差不多就这样昨完了。
代码:
#include <bits/stdc++.h>
using namespace std;
int T,n,m,hed[400010],cnt,q,hed2[400010],cnt2;
int sta[400010],tp,low[400010],z,ct;
int dfn[400010],dep[400010],f[400010][19],dis[400010];
int k,b[200010],ans;
struct node
{
int to,next;
}a[800010],aa[1600010];
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void add(int from,int to)
{
a[++cnt].to=to;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline void dfs(int x)
{
dfn[x]=++z;
dis[x]=dis[f[x][0]];
if(x<=n)
dis[x]++;
for(int i=1;i<=18;++i)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=hed2[x];i;i=aa[i].next)
{
int y=aa[i].to;
if(y==f[x][0])
continue;
f[y][0]=x;
dep[y]=dep[x]+1;
dfs(y);
}
}
inline void add2(int from,int to)
{
aa[++cnt2].to=to;
aa[cnt2].next=hed2[from];
hed2[from]=cnt2;
}
inline void tarjan(int x)
{
sta[++tp]=x;
low[x]=dfn[x]=++z;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
++ct;
if(dfn[x]<=low[y])
{
do
{
add2(ct,sta[tp]);
add2(sta[tp],ct);
--tp;
}while(sta[tp+1]!=y);
add2(x,ct);
add2(ct,x);
}
}
else
low[x]=min(low[x],dfn[y]);
}
}
inline int lca(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
for(int i=18;i>=0;--i)
{
if(dep[x]-dep[y]>=(1<<i))
x=f[x][i];
}
if(x==y)
return x;
for(int i=18;i>=0;--i)
{
if(f[x][i]!=f[y][i]&&dep[f[x][i]])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
inline int cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
inline void insert(int x)
{
if(tp==1)
{
ans+=dis[x]-dis[sta[tp]];
sta[++tp]=x;
return;
}
int z=lca(x,sta[tp]);
while(dfn[sta[tp-1]]>=dfn[z]&&tp>1)
--tp;
if(sta[tp]!=z)
sta[tp]=z;
ans+=dis[x]-dis[sta[tp]];
sta[++tp]=x;
}
int main()
{
scanf("%d",&T);
while(T--)
{
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(hed,0,sizeof(hed));
memset(hed2,0,sizeof(hed2));
memset(f,0,sizeof(f));
memset(dis,0,sizeof(dis));
cnt=0;
cnt2=0;
scanf("%d%d",&n,&m);
ct=n;
for(int i=1;i<=m;++i)
{
int x=read(),y=read();
add(x,y);
add(y,x);
}
z=0;
tp=0;
tarjan(1);
z=0;
dep[1]=1;
dfs(1);
scanf("%d",&q);
for(int i=1;i<=q;++i)
{
scanf("%d",&k);
for(int j=1;j<=k;++j)
b[j]=read();
sort(b+1,b+k+1,cmp);
int gg=b[1];
for(int j=2;j<=k;++j)
gg=lca(gg,b[j]);
tp=0;
sta[++tp]=gg;
ans=0;
if(gg<=n)
++ans;
for(int j=1;j<=k;++j)
insert(b[j]);
ans-=k;
printf("%d\n",ans);
}
}
return 0;
}