题目大意:
给定一个 \(n\) 个顶点, \(m\) 条边的图,现在对图进行 \(d\) 次操作, 每次操作为标记编号为 \(c\) 的顶点不能走(即删除与 \(c\) 相连的边),每次操作中又有 \(q\) 次询问:编号 \(x\) 和 编号 \(y\) 之间是否有通路。
思路:
这个题目如果采用每次询问一次就遍历图,那么复杂度显然是 \(5 * 10000 * 1000 * 1000\), 显然会超时。因此,这种暴力 \(dfs\) 模拟算法不可行。
仔细一想,这个题目每次询问可以采取离线处理方式,然后利用并查集考虑两点是否有通路,即两个点在同一个集合就是形成了通路。采用逆向处理,先删除所有标记的 \(c\) 点,然后从后往前加点,不断更新并查集。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 50010, M = 400010;
int n, m, d;
int h[N], e[M], ne[M], idx;
vector<int> s;
vector<pair<int, int> > query[1010];
int p[N], ans[1010];
bool st[N];
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int find(int x){
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
memset(h, -1, sizeof h);
cin >> n >> m >> d;
for(int i = 0; i < m; i++){
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
for(int i = 1; i <= n; i++) p[i] = i;
for(int i = 0; i < d; i++) {
int c, q;
cin >> c >> q;
st[c] = true;
s.push_back(c);
while(q --){
int x, y;
cin >> x >> y;
query[i].push_back({x, y});
}
}
for(int i = 1; i <= n; i++){
if(st[i] == false){
for(int j = h[i]; ~j; j = ne[j]){
int t = e[j];
if(st[t] == true) continue;
int fa = find(i), fb = find(t);
if(fa != fb) p[fa] = fb;
}
}
}
for(int i = d - 1; i >= 0; i--){
for(int j = 0; j < query[i].size(); j++){
int fx = find(query[i][j].first), fy = find(query[i][j].second);
if(fx != fy) ans[i]++;
}
st[s[i]] = false;
for(int k = h[s[i]]; ~k; k = ne[k]){
int t = e[k];
if(st[t] == false) p[find(t)] = find(s[i]);
}
}
for(int i = 0; i < d; i++){
cout << ans[i] << endl;
}
return 0;
}