原题链接:
PTA | 程序设计类实验辅助教学平台
题面:
疫情尚未结束,严防疫情反复。为了做好疫情防控工作,国内设置了地区风险等级,对于中高风险地区的人员采取限制移动、居家隔离等手段。
为了研究疫情防控对于跨地区交通运输的影响,假设现在有 N 个机场,M 条航线,每天都会新增一个防控地区,一个防控地区会导致一个机场无法正常运作,航线也自然无法正常运行,每天会有 Qi 对旅客从 Xi 机场前往 Yi 机场,请计算有多少对旅客会受到影响无法完成行程。
旅客只要能直达或通过若干次中转,且乘坐的所有航线的出发和到达机场都正常运作,即视作可完成行程。
输入格式:
输入第一行是三个整数 N,M,D (1≤N≤5×104, 1≤M≤2×105, 1≤D≤103), 表示机场数、航线数以及新增防控地区的天数。
接下来首先有 M 行,每行给出空格分隔的两个数字 A 和 B,表示编号为 A 和 B 的机场之间有一条航线。航线是双向的,机场编号从 1 到 N。
然后是 D 块输入,每块输入内第一行为空格分隔的两个整数 C 和 Q (1≤Q≤103),表示新增机场编号为 C 所在的城市为防控地区,今天有 Q 段行程。数据保证新增的城市之前一定不是防控地区。
接下来的 Q 行,每行是空格分隔的两个数字 X 和 Y,表示编号为 X 和 Y 的机场的一段行程。行程有可能包括之前就已经成为防控地区的城市。
输出格式:
对于每天的询问,请在一行中输出在新增了一个防控地区后当天的行程有多少不能成行。
输入样例:
5 5 3 1 2 1 3 1 5 2 5 3 4 4 3 1 3 1 4 2 3 5 3 3 4 2 3 3 5 1 3 2 3 2 5 3 4
输出样例:
1 2 3
题目大意:
给出n个点,m条边的无向图,然后在接下来的d天,每天删除一个c点,并询问q次u和v是否能够连通。
解题思路:
如果考虑最朴素的做法,每次删除一个点,并且dfs判断q对点对的连通性,复杂度会上天,最后一个数据点会TLE。
考虑优化,对于连通性的问题,我们很容易想到并查集,但这里如果正序处理的话,是删除点,这个操作用并查集不好实现。
我们考虑逆向思维,离线化所有的询问并将问题转换为“从最后一天开始添加点”,这样就可以用并查集解决了。连通性的判断从O(n)降低到了O(logn)。
具体来说:记录下第i天删了谁,有哪些询问。从d天开始往前,每天加一个点,然后并查集直接判断一下是否联通即可。注意加点的时候需要考虑当前点连向的点必须是没有被删的,不然会WA。
代码(CPP):
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 5e4 + 10;
const int INF = 0x3fffffff;
const int mod = 1000000007;
vector<int> G[maxn];
bool isDel[maxn];
int del[1010]; // 每一天被删除的点
map<int, vector<pair<int, int>>> query; // 每一天的行程查询
int ans[1010]; // 保存答案
int n, m;int fa[maxn];
void init() {for (int i = 1; i < maxn; i++) {fa[i] = i;}
}int findFa(int x) {if (x == fa[x]) {return x;}return fa[x] = findFa(fa[x]);
}void Union(int a, int b) {int faA = findFa(a);int faB = findFa(b);if (faA != faB) {fa[faB] = faA;}
}void solve() {int D;cin >> n >> m >> D;for (int i = 0; i < m; i++) {int u, v;cin >> u >> v;G[u].push_back(v);G[v].push_back(u);}// 记录被删除的点以及每天的行程查询,进行离线处理for (int d = 1; d <= D; d++) {int c, q;cin >> c >> q;isDel[c] = true;del[d] = c;while (q--) {int u, v;cin >> u >> v;query[d].push_back({u, v});}}// 初始化并查集init();// 把还没有删除的点放进并查集 for (int i = 1; i <= n; i++) {if (!isDel[i]) {for (int j = 0; j < G[i].size(); j++) {if (!isDel[G[i][j]]) {Union(i, G[i][j]);}}}}// 逆向思维:从最后一天依次添加点,这样就可以使用并查集解决这个问题了for (int d = D; d >= 1; d--) {int c = del[d];for (int i = 0; i < query[d].size(); i++) {int u = query[d][i].first, v = query[d][i].second;if (findFa(u) != findFa(v)) {ans[d]++; // 记录这一天的答案}}isDel[c] = false;for (int i = 0; i < G[c].size(); i++) {if (!isDel[G[c][i]]) {Union(c, G[c][i]);}}}for (int i = 1; i <= D; i++) {cout << ans[i] << endl;}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);solve();return 0;
}