传送门
补的第一道SDOI2018?
圆方树上建虚树 欢乐多又多【大雾
大概就是求对于S个点 问删掉一个点使它们不完全连通的方案数
那么我们可以看出 这个其实就是求两两路径并上的割点数量
那么 圆方树来解决是最好的办法【好像也没有别的办法
然后我们发现如果两两统计LCA的话,复杂度是S^2无法接受
可以看出 这个过程和虚树的构建过程十分相似
所以 我们就可以建出虚树 统计答案即可
附代码。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<stack>
#define inf 20021225
#define ll long long
#define mxm 200100
#define mxn 100100
using namespace std;
struct edge{int to,lt;}p[mxm<<1],t[mxm<<1],e[mxm<<1];
// p 原图 t 虚树 e 圆方树
int ip[mxn],it[mxn<<1],in[mxn<<1];
int cnp,cntt,cnt,pt,poi[mxm],n,m;
int dfn[mxn<<1],low[mxn],dep[mxn<<1],tot;
int f[mxn<<1][20],g[mxn<<1];
int stk[mxn<<1],num;bool spc[mxn<<1];
// 注意不要用混!!!
void add(int x,int y)
{
e[++cnt].to=y;e[cnt].lt=in[x];in[x]=cnt;
e[++cnt].to=x;e[cnt].lt=in[y];in[y]=cnt;
}
void app(int x,int y)
{
p[++cnp].to=y;p[cnp].lt=ip[x];ip[x]=cnp;
p[++cnp].to=x;p[cnp].lt=ip[y];ip[y]=cnp;
}
void att(int x,int y)//只建儿子边
{
t[++cntt].to=y;t[cntt].lt=it[x];it[x]=cntt;
}
stack<int> tt;
void tarjan(int x,int ff)
{
dfn[x]=low[x]=++tot;tt.push(x);
for(int i=ip[x];i;i=p[i].lt)
{
int y=p[i].to;if(y==ff) continue;
if(!dfn[y])
{
tarjan(y,x);
low[x]=min(low[y],low[x]);
if(low[y]>=dfn[x])
{
++num;add(num,x);int tmp;
do
{
tmp=tt.top();tt.pop();
add(num,tmp);
}while(tmp!=y);
}
}
else
low[x]=min(low[x],dfn[y]);
}
}
void dfs(int x)
{
dfn[x]=++tot;//记得清零!!
for(int i=1;i<20;i++)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=in[x];i;i=e[i].lt)
{
int y=e[i].to;if(dep[y]) continue;
f[y][0]=x;g[y]=g[x]+(y<=n);
dep[y]=dep[x]+1;dfs(y);
}
}
void maketree()//建圆方树
{
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i,i);
g[1]=dep[1]=1;tot=0;dfs(1);
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
int len=dep[x]-dep[y];
for(int i=19;~i;i--) if(len&(1<<i)) x=f[x][i];
if(x==y) return x;
for(int i=19;~i;i--) if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
void insert(int x)
{
if(!stk[0]){stk[++stk[0]]=x;return;}
int lca=LCA(stk[stk[0]],x);
if(lca!=stk[stk[0]])
while(stk[0]>1)
{
int z=stk[stk[0]-1];
if(dfn[z]>dfn[lca]) att(z,stk[stk[0]]),stk[0]--;
else if(z==lca){att(z,stk[stk[0]]);stk[0]--;break;}
else if(dfn[z]<dfn[lca]){att(lca,stk[stk[0]]),stk[stk[0]]=lca;break;}
}
if(stk[stk[0]]!=x) stk[++stk[0]]=x;
}
bool cmp(int x,int y){return dfn[x]<dfn[y];}
int ans,rt;//清零!!
void build()
{
sort(poi+1,poi+pt+1,cmp);
rt=poi[1];cntt=0;
for(int i=2;i<=pt;i++) rt=LCA(rt,poi[i]);
stk[0]=0;insert(rt);
for(int i=1;i<=pt;i++) insert(poi[i]);
while(stk[0]>1) att(stk[stk[0]-1],stk[stk[0]]),stk[0]--;
}
void query(int x)
{
for(int &i=it[x];i;i=t[i].lt)
{
int y=t[i].to;
ans+=g[y]-g[x];
query(y);
}
spc[x]=0;
}
void init()
{
memset(in,0,sizeof(in));
memset(ip,0,sizeof(ip));
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
memset(dep,0,sizeof(dep));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
cnt=cnp=tot=0;
while(!tt.empty()) tt.pop();
}
int main()
{
int T,x,y,q;
scanf("%d",&T);
while(T--)
{
init();
scanf("%d%d",&n,&m);num=n;
for(int i=1;i<=m;i++)
scanf("%d%d",&x,&y),app(x,y);
maketree();
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d",&pt);
for(int j=1;j<=pt;j++) scanf("%d",&poi[j]),spc[poi[j]]=1;
build();ans=(rt<=n)?1:0;query(rt);ans-=pt;
printf("%d
",ans);
}
}
return 0;
}