题解:ARC152F Attraction on Tree

尝试给出一个不太一样的做法。

显然若 \(\operatorname{dis}(1,n)\) 与 \(n\) 奇偶性不同则无解,下面设 \(m=\dfrac{n-\operatorname{dis}(1,n)}{2}\)。

把 \(n\) 视作树根。设 \(vis_u=[u\in\operatorname{path}(1,n)]\),对于一个非根点 \(u\),设从 \(fa_u\) 走到 \(u\) 的次数为 \(x_u\),则从 \(u\) 走到 \(fa_u\) 的次数就是 \(x_u+vis_u\),于是 \(\sum x_u=m\)。

最终经过的顶点形成一个包含 \(\operatorname{path}(1,n)\) 的连通块,其中不在路径上的点 \(u\) 要求 \(x_u\geq 1\)。对于任意一组 \(x\),取一个 \(1\) 到 \(n\) 的欧拉路径即可得到合法的移动方案,问题在于判定能否分配不同的操作点。

使用左部点带流量的 Hall 定理来刻画,左部点为 \(2(n-1)\) 类移动,右部点为 \(n\) 个操作点,对于每一类移动,向对应的操作点连边。具体来说,若左部点 \(p\) 表示从 \(fa_u\) 走到 \(u\),则给 \(p\) 传入 \(x_u\) 的流量,把 \(p\) 连向 \(sub_u\) 内的右部点;若 \(p\) 表示从 \(u\) 走到 \(fa_u\),则给 \(p\) 传入 \(x_u+vis_u\) 的流量,把 \(p\) 连向 \(sub_u\) 外的右部点。观察到这个二分图性质很好:对于任意两个左部点 \(p,q\),若 \(N(p)\cap N(q)\neq\varnothing\) 且 \(N(p)\) 与 \(N(q)\) 互不包含,则必然有 \(N(p)\cup N(q)=[n]\)。因此我们实际上只需要对于每个左部点 \(p\),判断是否有 \(\sum\limits_{N(q)\subseteq N(p)}f_q\leq |N(p)|\),其中 \(f_q\) 表示左部点 \(q\) 的流量。

设 \(S_u=\sum\limits_{v\in sub_u}x_v\)。若 \(p\) 表示从 \(fa_u\) 走到 \(u\),则判定条件为 \(S_u\leq sz_u\);若 \(p\) 表示从 \(u\) 走到 \(fa_u\),则 \(u\) 的后代都没有贡献,\(u\) 和 \(u\) 的祖先贡献 \(x_v+vis_v\),其他点贡献 \(x_v\),因此判定条件为 \(m-\sum\limits_{fa_v=u}S_v+c_u\leq n-sz_u\),其中 \(c_u\) 表示 \(\operatorname{path}(1,u)\) 和 \(\operatorname{path}(1,n)\) 的边交集大小。

整理一下,设 \(L_u=m+c_u+sz_u-n\),那么一组 \(x\) 合法的判定条件就是 \(S_u\leq sz_u\) 且 \(\sum\limits_{fa_v=u}S_v\geq L_u\)。

做一些观察,对于一种合法方案,猜测 \(S_u\) 的取值范围是一个区间 \([low_u,sz_u]\)。考虑直接归纳,固定 \(sub_u\) 内经过的点,对经过的儿子节点的 \(low,sz\) 分别求和得到 \(X,Y\),则 \(\sum\limits_{fa_v=u}S_v\) 可以取到 \([X,Y]\) 内的任意整数。我们要求 \(x_u\geq 1-vis_u\),因此取 \(low_u=\max(X,L_u)+(1-vis_u)\),则 \(S_u\) 可以取到 \([low_u,sz_u]\) 内的任意整数。这个性质启发我们只需记录子树内的 \(low_u\) 和最少经过点数。

考虑贪心,用 \(low_u\) 表示上述下界,\(mn_u\) 表示子树内的最少经过点数。满足 \(vis_v=1\) 或者 \(L_v>0\) 的儿子节点必须被经过,因此先扫一遍必须经过的儿子 \(v\),令:

\[\begin{align*} low_u&\gets low_u+low_v\\ mn_u&\gets mn_u+mn_v\\ R&\gets R+sz_v \end{align*} \]

再来考虑可选的儿子 \(v\)。考察 \(v\) 的后代 \(w\),由于 \(vis_v=0\land L_v\leq 0\),此时有 \(c_w=c_v\),也就是说 \(L_w\leq L_v\leq 0\),于是我们不经过的 \(w\) 的后代也不影响合法性!因此可选儿子 \(v\) 的作用就是耗费一个点,提供一个取值区间 \([1,sz_v]\)。于是我们将可选儿子按 \(sz_v\) 降序从大到小扫,若 \(R<L_u\) 就令

\[\begin{align*} low_u&\gets low_u+1\\ mn_u&\gets mn_u+1\\ R&\gets R+sz_v \end{align*} \]

最后再令 \(low_u\gets\max(low_u,L_u)+(1-vis_u)\) 即可。不难发现这种策略同时最小化了 \(low_u\) 和 \(mn_u\),正确性是有保证的。

根节点 \(n\) 需要单独处理,要求 \(\sum\limits_{fa_v=n}S_v=m\),检查 \(low_n\leq m\leq R\) 即可。

时间复杂度 \(\mathcal{O}(n\log{n})\)。

代码
#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 = 2e5 + 5;

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; }
constexpr int lg2(ll x) { return 63 ^ __builtin_clzll(x); }
constexpr ll bitCeil(ll x) { return x == 1 ? 1ll : 1ll << lg2(x - 1) + 1; }

int n, m, fa[MAXN], dep[MAXN], sz[MAXN];
int c[MAXN], L[MAXN];
int low[MAXN], mn[MAXN];
bool vis[MAXN];
vector<int> T[MAXN];

void dfs1(int u) {
	sz[u] = 1;
	for (int v : T[u]) {
		if (v == fa[u]) continue;
		fa[v] = u;
		dep[v] = dep[u] + 1;
		dfs1(v);
		sz[u] += sz[v];
	}
}

int dfs2(int u) {
	c[u] = vis[u] ? dep[u] : c[fa[u]];
	L[u] = m + c[u] + sz[u] - n;
	mn[u] = 1;
	int hi = 0;
	vector<int> vec;
	for (int v : T[u]) {
		if (v == fa[u]) continue;
		if (dfs2(v) == -1) return -1;
		if (vis[v] || L[v] > 0) {
			low[u] += low[v];
			hi += sz[v];
			mn[u] += mn[v];
		} else {
			vec.emplace_back(sz[v]);
		}
	}

	sort(vec.begin(), vec.end(), greater<>());
	for (int s : vec) {
		if (hi >= L[u]) break;
		++low[u];
		hi += s;
		++mn[u];
	}

	if (hi < L[u]) return -1;
	if (u == n) return low[u] <= m ? mn[u] : -1;
	low[u] = max(low[u], L[u]) + !vis[u];
	return mn[u] <= sz[u] ? 1 : -1;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n;
	for (int i = 1; i < n; ++i) {
		int u, v;
		cin >> u >> v;
		T[u].emplace_back(v);
		T[v].emplace_back(u);
	}

	dfs1(n);

	if (n - dep[1] & 1) {
		cout << "-1";
		return 0;
	}

	m = n - dep[1] >> 1;
	for (int p = 1; p; p = fa[p]) vis[p] = true;
	cout << dfs2(n);
	return 0;
}
posted @ 2026-10-01 12:24  P2441M  阅读(20)  评论(6)    收藏  举报