P2500 [SDOI2012]集合
[SDOI2012]集合
题目描述
小H在学习“集合与图论”的时候遇到了一个问题,他思考了很久依然无法很好完成这个问题。于是他只好来求助你了,给出n个点m条边的带权无向图(即每条无向边上都有一个权值),有3个集合A、B、C。一开始无向图中所有点都属于A集合,有如下9种操作:
MoveA x:表示将第x个点从所在集合中删除,并加入至A集合。
MoveB x:表示将第x个点从所在集合中删除,并加入至B集合。
MoveC x:表示将第x个点从所在集合中删除,并加入至C集合。
AskAA:询问两个端点都属于A集合的所有边中最小的权值是多少。
AskAB:询问两个端点分别属于A集合和B集合的所有边中最小的权值是多少。
AskAC:询问两个端点分别属于A集合和C集合的所有边中最小的权值是多少。
AskBB:询问两个端点都属于B集合的所有边中最小的权值是多少。
AskBC:询问两个端点分别属于B集合和C集合的所有边中最小的权值是多少。
AskCC:询问两个端点都属于C集合的所有边中最小的权值是多少。
你能帮助他解决这个问题吗?
输入格式
输入的第1行有两个正整数,分别表示n和m。
在第2行至第m+1行中,每行有三个正整数,分别为u、v、w。表示这条无向边的两个端点分别为u和v(u != v),且这个边的权值为w(w<=10^9)。
第m+2行有一个正整数q,表示有q个询问。
在第m+3行至第m+q+2行中,每行的输入方式为题目描述里9种操作中的一种。
输出格式
对于所有的Ask操作输出最小的权值,如果不存在则输出“No Found!”。
样例 #1
样例输入 #1
4 3
1 2 1
2 3 2
3 1 3
5
AskAA
AskAB
MoveB 2
AskAA
AskAB
样例输出 #1
1
No Found!
3
1
提示
数据范围
对于其中20%的数据,满足n<=50, m<=2500, q<=2500。
对于另外30%的数据,满足n<=100, m<=10000, q<=20000。
对于另外50%的数据,满足n<=100000,m<=500000,q<=100000。且无向图上任意两个点之间至多能选出3条不相交的路径。
Solution
容易发现使用一般的数据结构很难维护这类信息,因此先考虑暴力做法,然后对暴力进行优化(下述时间复杂度默认 \(n,m,q\) 同阶)。
对于前 \(50\%\) 的数据,容易想到对所有可能的询问分别建一个 set
维护满足条件的边权。具体来说,就是建 \(6\) 个 set
,分别维护 \(A\leftrightarrow A\),\(A\leftrightarrow B\),\(A\leftrightarrow C\),\(B\leftrightarrow B\),\(B\leftrightarrow C\),\(C\leftrightarrow C\) 的答案,然后对于每次修改直接暴力修改影响的 set
内的边权,具体写法很简单,不再赘述。
考虑怎么优化这个暴力。一个很典的 Trick 是将所有点按照度数进行根号分治,将度数 \(\ge \sqrt{n}\) 的点称为关键点,其余称为非关键点。容易发现,关键点的数目是 \(\mathcal O(\sqrt n)\) 级别的。
- 对于非关键点,考虑延续上面部分分的思路,由于每一个点的度数不超过 \(\sqrt n\),因此这一部分的边的数目是 \(\mathcal O(n\sqrt n)\) 级别的,所以可以同样去维护 \(6\) 个
set
来记录答案; - 对于关键点,可以对每一个关键点开 \(3\) 个
set
,表示关键点分别到三个集合的边集。因为关键点的数目是 \(\mathcal O(\sqrt n)\) 级别的,因此这部分边的数目也是 \(\mathcal O(n\sqrt n)\) 的。
关于修改:
- 对于非关键点,遍历所有出边 \(u\to v\),若 \(v\) 为关键点,那么修改 \(v\) 的
set
;否则修改 \(6\) 个set
的元素; - 对于关键点,遍历所有可到达的关键点 \(v\),修改 \(v\) 的
set
(这部分实现的时候可以先把所有点的出边按照是否是关键点排序,然后所有的出边为关键点的边就会被连续访问到); - 两类操作时间复杂度都是 \(\mathcal O(\sqrt n\log n)\) 的。
关于询问:
- 先在维护的 \(6\) 个
set
中查询非关键点间的答案; - 然后遍历当前集合中的关键点,在关键点的
set
中查询答案; - 第一步时间复杂度 \(\mathcal O(\log n)\),第二步时间复杂度 \(\mathcal O(\sqrt n\log n)\)。
总时间复杂度 \(\mathcal O(n\sqrt n\log n)\),常数很小,但是由于数据原因,跑的没有暴力快。
代码实现上注意一下判断 set
是否为空,否则会 RE。由于数据很水,甚至不需要 multiset
。
#include<bits/stdc++.h>
using namespace std;
template<class T> void read(T &x) {
x = 0; bool flag = 0; char b = getchar();
while (!isdigit(b)) flag = b == '-' ? 1 : 0, b = getchar();
while (isdigit(b)) x = x * 10 + b - 48, b = getchar();
x = flag ? -x : x;
}
template<class T, class ...Args> void read(T &x, Args &...args) {
read(x), read(args...);
}
constexpr int _N = 5e5 + 5, _SN = 1e3 + 5, inf = 1e9;
int n, m, q;
int X[_N], Y[_N], W[_N];
int deg[_N], MID;
set<int> f[5][5], crit[_SN][5], P[5];
int id[_N];
vector<pair<int, int>> edge[_N];
unordered_map<int, int> num;
int cnt = 0;
inline bool IsCri(int x) {return deg[x] >= MID;}
void Move(int x, int tar) {
if (!IsCri(x)) { // not critical
for (auto e : edge[x]) {
int v = e.first, w = e.second;
if (IsCri(v)) {
crit[num[v]][id[x]].erase(w);
crit[num[v]][tar].emplace(w);
} else {
int tx = min(id[x], id[v]), ty = max(id[x], id[v]);
int fx = min(tar, id[v]), fy = max(tar, id[v]);
f[tx][ty].erase(w);
f[fx][fy].emplace(w);
}
}
id[x] = tar;
} else { // critical
for (auto e : edge[x]) {
int v = e.first, w = e.second;
if (!IsCri(v)) break;
crit[num[v]][id[x]].erase(w);
crit[num[v]][tar].emplace(w);
}
P[id[x]].erase(x);
id[x] = tar;
P[tar].emplace(x);
}
}
int Query(int x, int y) {
int res = !f[x][y].empty() ? *f[x][y].begin() : inf;
if (!P[x].empty()) for (int u : P[x]) {
if (crit[num[u]][y].empty()) continue;
res = min(res, *crit[num[u]][y].begin());
}
if (!P[y].empty()) for (int u : P[y]) {
if (crit[num[u]][x].empty()) continue;
res = min(res, *crit[num[u]][x].begin());
}
return res;
}
signed main() {
ios::sync_with_stdio(0); cout.tie(0);
read(n, m);
for (int i = 1; i <= m; ++i) {
read(X[i], Y[i], W[i]);
++deg[X[i]], ++deg[Y[i]];
edge[X[i]].emplace_back(Y[i], W[i]);
edge[Y[i]].emplace_back(X[i], W[i]);
}
MID = sqrt(n);
for (int i = 1; i <= n; ++i) {
sort(edge[i].begin(), edge[i].end(), [](const auto &A, const auto &B) {
return IsCri(A.first) > IsCri(B.first);
});
if (IsCri(i)) num[i] = ++cnt;
}
for (int i = 1; i <= n; ++i) Move(i, 1);
read(q);
for (int i = 1; i <= q; ++i) {
char opt[15]; scanf("%s", opt + 1);
int x;
if (opt[1] == 'M') {
read(x);
Move(x, opt[5] - 'A' + 1);
} else {
int ans = Query(opt[4] - 'A' + 1, opt[5] - 'A' + 1);
if (ans != inf) cout << ans << '\n';
else cout << "No Found!" << '\n';
}
}
}