题目链接:
https://www.luogu.com.cn/problem/P1197
参考博客:
https://www.luogu.com.cn/blog/Shine-Sky/solution-p1197
这个博客超级清晰,建议复习时再反复学习
算法:
1:逆向并查集 2:链式前向星
思路:
1:如果我们去正着摧毁 ,想想都有点困难,要不 我们使用逆向思维哈哈哈
2:没错 这道题就把摧毁转换成修建,然后利用并查集判断是否联通
#include <bits/stdc++.h>
#define f(i,a,b) for(register int i=a;i<=b;i++)
#define fd(i,a,b) for(register int i=a;i>=b;i--)
using namespace std;
const int maxn=4e5+2;
int n,m,k,a,b,head[maxn],tot,total,broken[maxn],ans[maxn],father[maxn];
bool Broken[maxn];
struct edge
{
int next,from,to;
}e[maxn];
inline void add_edge(int u,int v)
{
e[++tot].from=u;
e[tot].next=head[u];
head[u]=tot;
e[tot].to=v;
}
inline int get_father(int x)
{
if(father[x]!=x) father[x]=get_father(father[x]);
return father[x];
//你爸爸的爸爸就是你的爸爸——反查理马特——并查集
//相当于直接找根,father数组存的是根节点,而不是父节点了
}
inline void union_(int u,int v)
{
u=get_father(u),v=get_father(v);
if(u!=v)father[u]=v;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>m;
f(i,0,n)
father[i]=i,head[i]=-1;//并查集初始化
f(i,1,m)
{
cin>>a>>b;//储存图,由于无向图存两遍
add_edge(a,b),add_edge(b,a);
}
cin>>k;
f(i,1,k)
{
cin>>broken[i];
Broken[broken[i]]=1;//标记砸坏了
}
total=n-k;//初始化为所有点都是单独存在的
f(i,1,2*m)
if(!Broken[e[i].from]&&!Broken[e[i].to]&&get_father(e[i].from)!=get_father(e[i].to))
{//要是起点和终点都没砸坏 而且他们并没有联通
total--;//连一条边 减一个联通体
union_(e[i].from,e[i].to);
}
ans[k+1]=total;//当前就是最后一次破坏后的个数
fd(i,k,1)
{//这里不需要初始化 需要从上一次的废墟上修建
total++;//修复一个点 联通体+1
Broken[broken[i]]=0;//修复
for(int j=head[broken[i]];j!=-1;j=e[j].next)
{//枚举每一个子点
if(!Broken[e[j].to]&&get_father(broken[i])!=get_father(e[j].to))
{
total--;//连一边减一个联通块
union_(broken[i],e[j].to);//合并这两个点
}
}
ans[i]=total;
}
f(i,1,k+1) cout<<ans[i]<<endl;
return 0;
}