P3247 [HNOI2016] 最小公倍数 题解

P3247 [HNOI2016] 最小公倍数 题解

第一眼上去没什么明显的思路。图上问题一般没有什么好的多项式复杂度算法来解决。观察数据范围,注意到 n,q5×104,是一个典型的根号复杂度算法,于是考虑分块来处理。注意到所求的不一定是简单路径,也就是在不超过所需要的 a,b 的情况下可以随便遍历。要分块处理,于是想到并查集维护连通性,顺手维护一下 a,b 的最大值。

现在考虑将它离线的具体方式:

注意到 a,b 的范围很大,因此对于询问分块的时间复杂度不太有保证。于是考虑对于边分块。我们将边按照 a 升序排序并分块,在解决每一块的问题时顺便揪出来和区间相关的询问。注意由于每一块之间的 a 取值可能重复,因此只需要揪出来最后一个使该区间为相关区间的询问,不然复杂度会爆炸。对于当前块外的部分,套路地用双指针维护,于是我们需要在一开始将询问按 b 排序,处理每一个块时将前面所有的块按照 b 排序,由于此时 a 已经合法,处理每个询问时双指针加入即可。对于块内的部分,每个询问遍历一次加入,结束询问后要撤销,因此用可撤销启发式合并的并查集。

现在分析复杂度。我们设块长为 k:对于询问排序的复杂度总和是 O(qlogq),对于当前块之前的处理是 O(m2logmk),当前块内的处理显然是 qk。那么认为 m2logmk=qk 时最小,此时 k=m2logmq 即可。但不知道为什么,事实上取 k=mlogq 是最快的,或许是没有考虑启发式合并并查集的 logk 吧。

代码:

#include <bits/stdc++.h>
#define N 100005
using namespace std;
int n, m;

struct DSU {
	int fa[N], siz[N], mxa[N], mxb[N];
	struct node {
		int x, y, fx, fy, mxa, mxb, mya, myb;
	};
	node stk[N];
	int top;
	int fnd(int x) {
		return x == fa[x] ? x : fnd(fa[x]);
	}
	void mge(int x, int y, int va, int vb) {
		x = fnd(x), y = fnd(y);
		if (siz[x] < siz[y]) swap(x, y);
		stk[++top] = {x, y, fa[x], fa[y], mxa[x], mxb[x], mxa[y], mxb[y]};
		fa[y] = x;
		if (x != y) siz[x] += siz[y];
		mxa[x] = max({mxa[x], mxa[y], va});
		mxb[x] = max({mxb[x], mxb[y], vb});
	}
	void del(int t) {
		while (1) {
			node p = stk[top--];
			int x = p.x, y = p.y;
			if (x != y) siz[x] -= siz[y];
			fa[x] = p.fx, fa[y] = p.fy;
			mxa[x] = p.mxa, mxb[x] = p.mxb;
			mxa[y] = p.mya, mxb[y] = p.myb;
			if (top == t) return;
		}
	}
	void init() {
		for (int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1, mxa[i] = mxb[i] = -1;
		top = 0;
	}
} dsu;

struct node {
	int x, y, a, b, id;
} e[N], q[N];
bool cmpa(node a, node b) {
	return a.a < b.a;
}
bool cmpb(node a, node b) {
	return a.b < b.b;
}
bool cmpe(int a, int b) {
	return e[a].b < e[b].b;
}
bool cmpq(int a, int b) {
	return q[a].b < q[b].b;
}
int B, bl[N], br[N], tot;

int ans[N];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	B = max((int)sqrt(m * log2(n)), 1), tot = m / B;
	for (int i = 1; i <= tot; i++) bl[i] = br[i - 1] + 1, br[i] = bl[i] + B - 1;
	if (br[tot] != m) {
		++tot;
		bl[tot] = br[tot - 1] + 1;
		br[tot] = m;
	}
	for (int i = 1; i <= m; i++) cin >> e[i].x >> e[i].y >> e[i].a >> e[i].b;
	sort(e + 1, e + 1 + m, cmpa);
	int T;
	cin >> T;
	for (int i = 1; i <= T; i++) cin >> q[i].x >> q[i].y >> q[i].a >> q[i].b, q[i].id = i;
	sort(q + 1, q + 1 + T, cmpb);
	bl[tot + 1] = br[tot + 1] = N - 1;
	e[N - 1] = {-1, -1, -1, -1};
	for (int p = 1; p <= tot; p++) {
		dsu.init();
		vector<int>qu, egbf, egnw;
		int la = e[bl[p]].a, ra = e[br[p]].a, lla = e[bl[p + 1]].a, rra = e[br[p + 1]].a;
		for (int i = 1; i <= T; i++)
			if ((la <= q[i].a && q[i].a <= ra) && !(lla <= q[i].a && q[i].a <= rra)) qu.push_back(i);
		sort(qu.begin(), qu.end(), cmpq);
		for (int i = 1; i < bl[p]; i++)
			egbf.push_back(i);
		for (int i = bl[p]; i <= br[p]; i++)
			egnw.push_back(i);
		sort(egbf.begin(), egbf.end(), cmpe);
		sort(egnw.begin(), egnw.end(), cmpe);
		int t = -1;
		for (auto i : qu) {
			while (t + 1 < (int)egbf.size() && q[i].b >= e[egbf[t + 1]].b) {
				++t;
				dsu.mge(e[egbf[t]].x, e[egbf[t]].y, e[egbf[t]].a, e[egbf[t]].b);
			}
			int nt = dsu.top;
			for (auto j : egnw) {
				if (q[i].a >= e[j].a && q[i].b >= e[j].b) dsu.mge(e[j].x, e[j].y, e[j].a, e[j].b);
			}
			int fx = dsu.fnd(q[i].x), fy = dsu.fnd(q[i].y);
			if (fx == fy && q[i].a == dsu.mxa[fx] && q[i].b == dsu.mxb[fx])
				ans[q[i].id] = 1;
			if (dsu.top != nt) dsu.del(nt);
		}
	}
	for (int i = 1; i <= T; i++) {
		if (ans[i]) cout << "Yes\n";
		else cout << "No\n";
	}
	return 0;
}
posted @   长安19路  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示