题意简述
给定一个无向图,和 k k k个删除操作。第一行输出最开始有多少个联通块,接下来 k k k行输出删除每个点后,剩下的点组成多少个联通块。
样例
(输入输出格式懒得给了,以后也不想给了。。。大致明白题意即可。输入输出处理就是细节部分了。)
输入
8 13
0 1
1 6
6 5
5 0
0 6
1 2
2 3
3 4
4 5
7 1
7 2
7 6
3 6
5
1
6
3
5
7
输出
1
1
1
2
3
3
思路
我们知道并查集是珂以维护连边的,很好维护。
但是我们如何处理删边呢?
(可持久化)
显然不好写,也很明显不是最优解。我们考虑离线。经过思考后会发现,删除是加边的逆运算,我们只要“时间倒流”,先求删完之后的联通块个数,然后一个一个把边加回去,从后往前求。这样就只涉及加边的操作,就不用维护删边了。
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define N 400100
#define p_b push_back
vector<int> to[N];//维护最开始的图
int n,m,k;
int fk[N],ans[N];
bool vis[N];
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(n),R1(m);
for(int i=1;i<=m;++i)
{
int a,b;
R1(a),R1(b);
++a,++b;//我是习惯从1开始编号的。。。
to[a].p_b(b);
to[b].p_b(a);
}
R1(k);
for(int i=1;i<=k;++i)
{
R1(fk[i]);
++fk[i];
vis[fk[i]]=1;
}
}
class DSU//并查集
{
public:
int Father[N],Cnt[N];
void Init()
{
for(int i=0;i<N;i++)
{
Father[i]=i;
Cnt[i]=1;
}
}
int Find(int x)
{
return (x==Father[x])?x:(Father[x]=Find(Father[x]));
}
bool Merge(int x,int y)
{
int ax=Find(x),ay=Find(y);
if (ax==ay) return 0;
if (Cnt[ax]<Cnt[ay])
{
Cnt[ay]+=Cnt[ax];
Father[ax]=ay;
}
else
{
Cnt[ax]+=Cnt[ay];
Father[ay]=ax;
}
return 1;
}
}D;
void Soviet()
{
D.Init();//别忘了初始化
int cnt=n;//联通块个数
for(int u=1;u<=n;++u)
{
if (!vis[u])
{
for(int j=0;j<to[u].size();++j)
{
int v=to[u][j];
if (!vis[v])
{
cnt-=D.Merge(u,v);
}
}
}
}//处理最后一个答案
ans[k+1]=cnt-k;//注意要减去k,因为被删除的点是不会被计算到联通块里的
for(int i=k;i>=1;--i)
{
int u=fk[i];
vis[u]=0;
for(int j=0;j<to[u].size();++j)
{
int v=to[u][j];
if (!vis[v])
{
cnt-=D.Merge(u,v);//重新连上u,v的边
}
}
ans[i]=cnt-(i-1);//同理要减去(i-1)
}
for(int i=1;i<=k+1;++i)
{
printf("%d\n",ans[i]);//输出答案
}putchar('\n');
}
void IsMyWife()
{
if (1)
{
// freopen("","r",stdin);
// freopen("ans.txt","w",stdout);
}
Input();
Soviet();
}
};
int main()
{
Flandle_Scarlet::IsMyWife();
return 0;
}