P2500 [SDOI2012]集合

[SDOI2012]集合

Luogu P2500 [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';
		}
	}
}
posted @ 2023-03-22 21:27  Hanx16Msgr  阅读(25)  评论(0编辑  收藏  举报