Loading

【题解】[Mivik Round / 梦境彼岸] 泪光 | Tears

梦境彼岸

声海、泪光、再会、彼岸。

后会有期。

题意

[Mivik Round] 梦境彼岸 泪光 | Tears

\(n\) 个正实数变量 \(v1, ..., v_n\)\(m\) 个询问,根据已知条件回答询问。

每次给出两种条件之一:

  • 给出常数 \(a, b, c, d\),表示现在已知存在无穷个 \(f:\mathbb{R} \rightarrow \mathbb{R}\),使得 \(\frac{f(v_a)}{f(v_b)} = \frac{v_c}{v_d}\)
  • 给出常数 \(a, b\),表示现在已知存在有穷个 \(f:\mathbb{R} \rightarrow \mathbb{R}\),使得 \(f(v_a) \neq f(v_b)\)

其中 \(f:\mathbb{R} \rightarrow \mathbb{R}\) 表示一个定义域和值域都是实数的函数。

有穷个的定义是可以用一自然数 \(k\) 表示满足条件的函数个数。

或者两种询问之一:

  • 给出常数 \(a, b\),询问 \(v_a = v_b\) 是否恒成立。
  • 给出常数 \(a\),询问 \(\sum\limits_{1 \leq b \leq n} [v_a = v_b]\)

初始时可以认为所有变量互不相同,之后根据条件自行判断。

对于全部数据,有 \(1 \leq n, m \leq 6 \times 10^5\)

思路

转化 + 并查集。

对于第二种条件,显然当 \(v_a \neq v_b\) 时容易构造出无穷个满足条件的函数,当且仅当 \(v_a = v_b\) 时能构造出零个(有穷个)满足条件的函数。因此第二种条件实际上等价于 \(v_a = v_b\)

对于第一种条件,若 \(v_a \neq v_b\),显然容易构造出无穷个满足条件的函数,该条件无用。若 \(v_a = v_b\),此时 \(f(v_a) = f(v_b)\),即 \(\frac{v_c}{v_d} = 1\)。因此第一种条件相当于若 \(v_a = v_b\),则 \(v_c = c_d\)

考虑用并查集维护答案。对于操作二,直接并查集合并。对于操作一,当 \(n \leq 5000\) 时可以暴力 \(\mathcal{O}(n^2)\) 枚举此前的操作一维护答案,可以拿到 \(\texttt{Subtask 3}\)\(35\) 分。

对于所有数据,可以考虑给每个并查集附上与其内部结点有关的条件标记。对于第 \(i\) 个操作一,若 \(a, b\) 属于同一并查集,则直接合并 \(c, d\) 所属的并查集;反之,向 \(a, b\) 所属的并查集的根附上当前操作的标记 \(i\),表示以后维护与该并查集有关的操作二时需要检查是否符合第 \(i\) 个操作一的条件。

由于我们向两个并查集都加入了标记,所以检查时只需要检查其中一个并查集的标记即可。考虑使用启发式合并优化,每次检查标记数较少的并查集并且将它的条件标记加入另一并查集。注意一次合并可能会带出多次合并,因此应该使用队列存储当前需要合并的结点。

set 实现,时间复杂度为 \(\mathcal{O}(n \log^2 n)\)

代码

#include <cstdio>
#include <set>
#include <queue>
using namespace std;

const int maxn = 6e5 + 5;
const int maxm = 6e5 + 5;

int n, m;
int fa[maxn], siz[maxn];
int a[maxm], b[maxm], c[maxm], d[maxm];
bool vis[maxm];
set<int> st[maxn];
queue<pair<int, int> > q;

int get(int x) {
	if (fa[x] == x) {
		return x;
	}
	return fa[x] = get(fa[x]);
}

void merge(int x, int y) {
	x = get(x);
	y = get(y);
	if (x != y) {
		if (st[x].size() < st[y].size()) {
			int temp = x;
			x = y, y = temp;
		}
		for (set<int>::iterator it = st[y].begin(); it != st[y].end(); it++) {
			int cur = *it;
			if (vis[cur]) {
				continue;
			}
			if ((get(a[cur]) == x) && (get(b[cur]) == y)) {
				vis[cur] = true;
				st[x].erase(cur);
				q.push(make_pair(c[cur], d[cur]));
			} else if ((get(a[cur]) == y) && (get(b[cur]) == x)) {
				vis[cur] = true;
				st[x].erase(cur);
				q.push(make_pair(c[cur], d[cur]));
			}
		}
		for (set<int>::iterator it = st[y].begin(); it != st[y].end(); it++) {
			int cur = *it;
			if (!vis[cur]) {
				st[x].insert(cur);
			}
		}
		fa[y] = x;
		siz[x] += siz[y];
	}
}

void modify() {
	while (!q.empty()) {
		int x = q.front().first;
		int y = q.front().second;
		q.pop();
		merge(x, y);
	}
}

int main() {
	int opt;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
		siz[i] = 1;
	}
	for (int i = 1; i <= m; i++) {
		scanf("%d", &opt);
		if (opt == 1) {
			scanf("%d%d%d%d", &a[i], &b[i], &c[i], &d[i]);
			if (get(a[i]) == get(b[i])) {
				q.push(make_pair(c[i], d[i]));
				modify();
				vis[i] = true;
				continue;
			}
			st[get(a[i])].insert(i);
			st[get(b[i])].insert(i);
		} else if (opt == 2) {
			scanf("%d%d", &a[i], &b[i]);
			q.push(make_pair(a[i], b[i]));
			modify();
		} else if (opt == 3) {
			scanf("%d%d", &a[i], &b[i]);
			puts((get(a[i]) == get(b[i])) ? "entangled" : "separate");
		} else {
			scanf("%d", &a[i]);
			printf("%d\n", siz[get(a[i])]);
		}
	}
	return 0;
}
posted @ 2022-02-12 20:08  kymru  阅读(64)  评论(0编辑  收藏  举报