题解:P12799 [NERC 2022] Jumbled Trees

提供一个至多 \(m\) 次的做法。

一个经典结论是,任取无向连通图 \(G\) 的两棵生成树 \(T_1,T_2\),则 \(T_1\) 必然可以通过若干次“加入一条非树边,删去一条树边”的操作变成 \(T_2\)

证明

显然存在一条 \(T_2\) 中的树边 \(e\),使得 \(e\)\(T_1\) 中的非树边。\(e\) 与对应的一些树边在 \(T_1\) 中会构成一个环,这个环中又会存在一条 \(T_1\) 中的树边 \(f\),使得 \(f\)\(T_2\) 中为非树边(否则整个环上的边都是 \(T_2\) 中的非树边),我们令 \(T_1\gets T_1\setminus\{f\}\cup\{e\}\),则 \(T_1\)\(T_2\) 的公共边数量恰好增加 \(1\)。不断对 \(T_1\) 做这样的操作,\(T_1\) 最终必然会变成 \(T_2\)\(\Box\)

任取 \(G\) 的一棵生成树 \(T\),那么每次操作都可以分解成:对 \(T\) 做一次 \(+v\) 的操作,再做若干次“给一条边 \(+v\),另一条边 \(-v\)”的操作,下文中称为交换操作。并非任意两条边都可以做交换操作,必须存在某棵生成树 \(T'\),使得其中一条边是树边,另一条是非树边,因此这两条边必须同处于一个环中。不妨将所有边划分成若干等价类,对于 \(T\) 外的每条非树边 \((u,v)\),将其与 \(\operatorname{path}(u,v)\) 上的所有树边所在的等价类合并。这样同一个等价类里的任意两条边都可以执行交换操作。可以发现一个性质:对于一个等价类 \(S\),任意一棵生成树 \(T\)\(S\) 的交集大小是固定的。设所有操作的 \(v\) 之和为 \(D\),对于一个等价类 \(S\),设 \(|T\cap S|\) 这个固定的值为 \(c_S\),则可以得到条件:

\[\sum_{e\in S}x_e\equiv c_SD\pmod{p} \]

若存在 \(S\) 使得 \(c_S\bmod{p}=0\land \sum\limits_{e\in S}x_e\bmod{p}\neq 0\),则显然无解。对于每个 \(S\),都可以解出一个 \(D\),若这些 \(D\) 不全部相同,则同样无解;否则我们可以直接确定 \(D\) 的取值。

我们得到了一些判定有解的必要条件,猜测这也是充分的,尝试给出构造。先对 \(T\) 做一次 \(+D\) 的操作,那么一条边 \(e\) 还需要补上的权值为

\[x_e'=\begin{cases} x_e & \text{if }e\notin T\\ x_e-D & \text{if }e\in T \end{cases} \]

注意到此时一个等价类 \(S\) 内总的需求量恰好为

\[\sum_{e\in S}x_e-c_SD\equiv 0\pmod{p} \]

在合并等价类时,我们每次合并的都是 \(T\) 中的一条树边和一条非树边,对这个合并的过程提取出一个树结构,每个点对应原图中的一条边,每条边连接一条树边和一条非树边。对这个树结构自底向上递归操作,考虑当前的一对节点 \((u,fa_u)\),若 \(fa_u\) 为非树边,\(u\) 为树边,则我们对 \(T\) 做一次 \(+x_u'\) 的操作,再对 \(T\setminus\{u\}\cup\{fa_u\}\) 做一次 \(-x_u'\) 的操作,则 \(u\) 的权值恰好被补到了 \(x_u\),再令 \(x'_{fa_u}\gets x'_{fa_u}+x'_u\) 即可。另一种情况也是类似的。记录对 \(T\) 进行操作的总权值,最后输出即可。

这样我们就完成了构造,操作次数为 \(m+1-c\leq m\),其中 \(c\) 为等价类个数。时间复杂度为 \(\mathcal{O}(nm\log{m})/\mathcal{O}(nm\alpha(m))\)

代码
#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using i128 = __int128;
using ui = unsigned int;
using ull = unsigned long long;
using u128 = unsigned __int128;
using ld = long double;
using pii = pair<int, int>;
const int MAXN = 505, MAXM = 1005;

template<typename T> T lowbit(T x) { return x & -x; }
template<typename T> void chkMin(T &x, T y) { x = y < x ? y : x; }
template<typename T> void chkMax(T &x, T y) { x = x < y ? y : x; }

int n, m, mod, base;
int dep[MAXN], fa[MAXN], faId[MAXN];
int val[MAXM], cnt[MAXM], sum[MAXM];
bool visT[MAXM];
pii edges[MAXM];
vector<pii> G[MAXN];
vector<int> T[MAXM], edgesT;

template<typename T> T add(T x, T y) { return (x += y) >= mod ? x - mod : x; }
template<typename T> T sub(T x, T y) { return (x -= y) < 0 ? x + mod : x; }
template<typename T> void cadd(T &x, T y) { (x += y) >= mod ? (x -= mod) : x; }
template<typename T> void csub(T &x, T y) { (x -= y) < 0 ? (x += mod) : x; }

int qpow(int a, int b) {
	int res = 1;
	for (; b; b >>= 1) {
		if (b & 1) res = (ll)res * a % mod;
		a = (ll)a * a % mod;
	}
	return res;
}

struct DSU {
	int fa[MAXM];
	void init(int n) { iota(fa + 1, fa + n + 1, 1); }
	int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
	void unite(int x, int y) { fa[find(x)] = find(y); }
} dsu;

void dfs1(int u) {
	for (auto [v, id] : G[u]) {
		if (id == faId[u] || dep[v]) continue;
		visT[id] = 1;
		fa[v] = u;
		faId[v] = id;
		dep[v] = dep[u] + 1;
		dfs1(v);
	}
}

void dfs2(int u, int faU) {
	for (int v : T[u]) {
		if (v == faU) continue;
		dfs2(v, u);
		int c = visT[v] ? sub(0, val[v]) : val[v];
		cout << c << ' ';
		for (int e : edgesT) {
			if (e == u || e == v) cout << (e == u ? v : u) << ' ';
			else cout << e << ' ';
		}
		cout << '\n';
		csub(base, c);
		cadd(val[u], val[v]);
	}
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> m >> mod;
	for (int i = 1; i <= m; ++i) {
		int u, v;
		cin >> u >> v >> val[i];
		G[u].emplace_back(v, i);
		G[v].emplace_back(u, i);
		edges[i] = {u, v};
	}
	dep[1] = 1;
	dfs1(1);
	dsu.init(m);
	for (int i = 1; i <= m; ++i) {
		if (visT[i]) {
			edgesT.emplace_back(i);
			continue;
		}
		auto [u, v] = edges[i];
		if (dep[u] > dep[v]) swap(u, v);
		while (v != u) {
			int id = faId[v];
			if (dsu.find(id) != dsu.find(i)) {
				dsu.unite(id, i);
				T[id].emplace_back(i);
				T[i].emplace_back(id);
			}
			v = fa[v];
		}
	}
	vector<int> root;
	for (int i = 1; i <= m; ++i) {
		int rt = dsu.find(i);
		if (i == rt) root.emplace_back(rt);
		if (visT[i]) cadd(cnt[rt], 1);
		cadd(sum[rt], val[i]);
	}
	int D = -1;
	for (int i : root) {
		if (!cnt[i]) {
			if (sum[i]) return cout << "-1", 0;
			else continue;
		}
		int val = (ull)sum[i] * qpow(cnt[i], mod - 2) % mod;
		if (~D && D != val) return cout << "-1", 0;
		D = val;
	}
	if (!~D) D = 0;
	base = D;
	for (int id : edgesT) csub(val[id], D);
	cout << m - root.size() + 1 << '\n';
	for (int i : root) dfs2(i, 0);
	cout << base << ' ';
	for (int e : edgesT) cout << e << ' ';
	return 0;
}
posted @ 2026-05-04 18:31  P2441M  阅读(14)  评论(0)    收藏  举报