https://www.luogu.org/problem/show?pid=1197
思路
首先动态求割点会TLE,考虑倒序离线操作。
当所有打击完成后,Tarjin统计同一连通分量上的点,并查集维护连通性(实际上来说只要两点之间有边则将之合并即可Tarjin处理麻烦反而会MLE?..不清楚为什么)。再依次将被毁灭的星球复原,则对答案的影响只与当前星球连接的星球数量(及连通关系)有关,每次统计当前连通块个数。
总而言之写麻烦了…
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,m,tot,cnt,num,root,ru,rv,k,sum;
int first[500010],nxt[500010],dfn[200010],low[200010],sccno[200010],stak[500010],fa[200010],xs[200010],scc[200010],ans[200010];
bool cut[200010],b[200010];
struct edge
{
int u,v;
}l[500010];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void build(int f,int t)
{
l[++tot]=(edge){f,t};
nxt[tot]=first[f];
first[f]=tot;
}
void dfs(int k)
{
dfn[k]=low[k]=++tot;
stak[++num]=k;
for(int i=first[k];i!=-1;i=nxt[i])
{
int x=l[i].v;
if(cut[x])
continue;
if(!dfn[x])
{
dfs(x);
low[k]=min(low[k],low[x]);
}
else if(!sccno[x])
low[k]=min(low[k],dfn[x]);
}
if(low[k]==dfn[k])
{
cnt++;
while(1)
{
int p=stak[num--];//将打击完成后图中同一连通块内元素合并
int u=find(p); //其实只要两点之间有边合并即可不需要Tarjin...
int v=find(k);
if(u!=v)
fa[u]=k;
sccno[p]=cnt;
if(stak[num+1]==k)
break;
}
}
}
int main()
{
memset(first,-1,sizeof(first));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&ru,&rv);
build(ru,rv);
build(rv,ru);
}
scanf("%d",&k);
for(int i=1;i<=k;i++)
{
scanf("%d",&xs[i]);
cut[xs[i]]=1;
}
for(int i=0;i<=n-1;i++)
fa[i]=i;
tot=0;
for(int i=0;i<=n-1;i++)
{
if(!dfn[i]&&!cut[i])//去除被毁灭的星球
root=i,dfs(i);
}
sum=cnt;
num=0;
for(int i=k;i>=1;i--)//打击完成后倒序处理
{
tot=0;
int h=xs[i];
cut[h]=0;//复原
for(int i=first[h];i!=-1;i=nxt[i])//便利出边
{
int x=l[i].v;
if(cut[x])//仍处于毁灭状态的不可合并
continue;
int p=find(x);
if(!b[p])//每个集合只合并一次,亦可以通过找fa判断
{
tot++;//统计此次合并的集合个数除了h点
scc[tot]=p;
b[p]=1;
}
}
for(int i=1;i<=tot;i++)
fa[scc[i]]=h,scc[i]=0;
cnt=cnt-tot+1;//+1为加上h点
ans[++num]=cnt;//统计当前答案
}
for(int i=num;i>=1;i--)//转正序输出
printf("%d\n",ans[i]);
printf("%d\n",sum);
return 0;
}