题目模型:
给出n个点,m条边。
k次操作,每次删掉一个点及与其相连的边,给出若干个数对(x,y),问 点x 和 点y 是否在同一连通块中?
暴力做法:
对于每个数对都跑一遍bfs,超时。
需要用并查集。
如果按照给出的顺序删点的话,这个点倒是删了,那已经合并的连通块如何更新呢?就没法搞了。
想象一下两种状态。
一种状态为:正着来删点。原来所有点都没删,然后进行过k-1次操作之后,仅剩最后一个删点没有删,询问两个点x,y是否在同一连通块中。
另一种状态为:倒着来加点。连边时,删点不要参与连边。然后加入最后一个操作的删点,询问两个点x,y是否在同一连通块中。
我们可以发现,这两种状态的结果是相同的。
最后剩两个删点 和 只添加最后两个删点 这两种状态的结果也是相同的。
…
既然正着来和倒着来结果相同,所以可以用倒序的思想,将所有询问保存下来,从后往前处理。
连边时,删点不参与。
然后从后往前将操作数中的删点加入到集合中,合并原本与其相邻的点(注意不要连接前面的还没加到的删点),然后进行对应的查询操作。
例题1、7-4 疫情防控 (30 分)
题意:
初始给出一个 n 个点 m 条边的无向图,一共 K 次操作。
对于第 i 次操作:
删除一个点,与其相连的所有边相应删除,然后给出 qi 次询问,x 和 y 是否可达?
Code:
#include<bits/stdc++.h>
using namespace std;
map<int,int> mp;
/*
初始给出一个 n 个点 m 条边的无向图。
第 i 次操作:
删除一个点,与其相连的所有边相应删除,然后给出 qi 次询问,x 和 y 是否可达?
集合合并之后再拆开不好处理,那么就反着来,处理之后再合并。
逆序处理,将需要删除的点别慌连边,将其相邻点记录。
将所有操作记录,从后往前处理:
判断两点是否可达后,将该点插入图中,与相邻点合并。
*/
const int N = 200010, mod = 1e9+7;
int T, n, m;
PII a[N];
int b[N], pre[N], ans[N];
vector<PII> v[N];
vector<int> e[N];
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
signed main(){
Ios;
int k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++) pre[i] = i;
for(int i=1;i<=m;i++) cin>>a[i].fi>>a[i].se; //初始连边
for(int i=1;i<=k;i++)
{
int x, cnt;
cin>>x>>cnt;
b[i] = x;
mp[x] = 1;
while(cnt--) //对于此次删除的询问
{
int a, b;cin>>a>>b;
v[i].pb({a, b});
}
}
for(int i=1;i<=m;i++)
{
int x = a[i].fi, y = a[i].se;
if(mp[x] || mp[y]) //如果后面要删掉,就别连边,记录每个删点的邻点
{
if(mp[x]) e[x].pb(y);
if(mp[y]) e[y].pb(x); //这里不能用else
}
else pre[find(x)] = find(y);
}
for(int i=k;i>=1;i--) //从后往前判断
{
for(auto it:v[i]) //对于此次删点的询问
{
int x = it.fi, y = it.se;
if(find(x) != find(y)) ans[i]++;
}
for(auto tx:e[b[i]]) //把当前点加上,和邻点合并
{
if(mp[tx]) continue; //保证邻点没被删掉
pre[find(tx)] = find(b[i]);
}
mp[b[i]] = 0; //当前点的删除标记消除
}
for(int i=1;i<=k;i++) cout<<ans[i]<<'\n';
return 0;
}
例题2、[JSOI2008]星球大战
题意:
每次删除一个点,求当前连通块个数。
Code:
/*
如何计算加上一个点几条边之后,连通块的个数呢?
判断是否有一条边连接的连接的两点不在同一连通块,加上这条边之后,两个连通块合并,
连通块的个数-1。
*/
const int N = 400010, mod = 1e9+7;
int T, n, m;
PII a[N];
int b[N],pre[N],ans[N];
vector<int> v[N];
int find(int x){
int t=x;
while(pre[x]!=x) x=pre[x];
pre[t]=x;
return x;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i].first>>a[i].second;
}
int k;cin>>k;
for(int i=1;i<=k;i++){
cin>>b[i];mp[b[i]]=1;
}
for(int i=0;i<n;i++) pre[i]=i;
for(int i=1;i<=m;i++){
int x=a[i].first,y=a[i].second;
if(mp[x]||mp[y]){
if(mp[x]) v[x].push_back(y);
if(mp[y]) v[y].push_back(x);
}
else pre[find(x)]=find(y);
}
int cnt=0;
for(int i=0;i<n;i++){
if(!f[find(i)]) f[find(i)]=1,cnt++;
}
ans[k+1]=cnt;
for(int i=k;i>=1;i--)
{
int x=b[i];
for(int j=0;j<v[x].size();j++){
int y=v[x][j];
if(!mp[y]){
if(find(x)!=find(y)) f[find(x)]=0,cnt--;
pre[find(x)]=find(y);
}
}
ans[i]=cnt;
mp[b[i]]=0;
}
for(int i=1;i<=k+1;i++) cout<<ans[i]-i+1<<endl;
return 0;
}