题解:P12653 [KOI 2024 Round 2] 分数竞赛

题意

给定一棵 \(n\) 个点的树,边有权值。对于一条从 \(u\)\(v\) 的路径:

  • 初始时 \(s=0,c=0\)
  • 每经过一条权值为 \(w\) 的边,令 \(s\gets s+w\)。若 \(s<0\),则令 \(s\gets 0,c\gets c+1\)

对于每个点 \(u\),求出所有从 \(u\) 出发的路径的 \(s\) 之和与 \(c\) 之和。\(n\leq 3\times 10^5\)

题解

容易将 \(c\) 转化为严格前缀最小值个数,\(s\) 转化为路径边权和减去最小前缀和。

对于一条从 \(u\)\(v\) 的路径,定义 \(\operatorname{sum}(u,v)\) 为路径边权和,\(\operatorname{minpre}(u,v)\) 为最小前缀和,\(\operatorname{cnt}(u,v)\) 为路径上的严格前缀最小值个数。考虑点分治,尝试合并 \(u\) 走到 \(rt\) 的信息和 \(rt\) 走到 \(v\) 的信息:

\[\begin{align*} \operatorname{sum}(u,v)&=\operatorname{sum}(u,rt)+\operatorname{sum}(rt,v)\\ \operatorname{minpre}(u,v)&=\min(\operatorname{minpre}(u,rt),\operatorname{sum}(u,rt)+\operatorname{minpre}(rt,v))\\ \operatorname{cnt}(u,v)&=\operatorname{cnt}(u,rt)+\sum_{x\in\operatorname{path}(bel_v,v)}[\operatorname{sum}(rt,x)<\operatorname{minpre}(rt,fa_x)][\operatorname{sum}(u,rt)+\operatorname{sum}(rt,x)<\operatorname{minpre}(u,rt)] \end{align*} \]

其中 \(bel_v\) 表示 \(v\) 位于 \(rt\) 的哪个儿子的子树中。

先来做第一问,我们要求 \(\sum\limits_v \operatorname{sum}(u,v)-\operatorname{minpre}(u,v)\)。拆成两部分之差,前者是容易求的。对于后者,考察 \(\operatorname{minpre}(u,v)\) 取到 \(\operatorname{minpre}(u,rt)\) 的条件是 \(\operatorname{minpre}(u,rt)-\operatorname{sum}(u,rt)\leq \operatorname{minpre}(rt,v)\),取到 \(\operatorname{sum}(u,rt)+\operatorname{minpre}(rt,v)\) 的条件则反之。那么用一棵动态开点权值线段树维护 \(\operatorname{minpre}(rt,v)\) 的个数和总和即可。

再来做第二问,我们要求 \(\sum\limits_v \operatorname{cnt}(u,v)\)。考察 \(\sum\) 内部的两个条件,第一个条件是容易判断的,对于第二个条件,将其变为 \(\operatorname{sum}(rt,x)<\operatorname{minpre}(u,rt)-\operatorname{sum}(u,rt)\)。可以得到一个做法:对于每一个 \(v\),枚举 \(\operatorname{path}(bel_v,v)\) 上的点 \(x\),将 \(\operatorname{sum}(rt,x)\) 插到另一棵维护个数的动态开点权值线段树中。反过来考虑,对于每个 \(x\),有 \(sz_x\)\(v\) 可以贡献到它,改为在 \(\operatorname{sum}(rt,x)\) 的位置单点加 \(sz_x\) 即可。

还需要求出 \(u\) 走到 \(rt\) 的信息和 \(rt\) 走到 \(v\) 的信息。后者是容易递推求出的。对于前者,考虑找到 \(u\) 最深的祖先 \(anc_u\) 使得 \(\operatorname{sum}(u,rt)<\operatorname{sum}(anc_u,rt)\),其意义为从 \(u\) 出发向 \(rt\) 走,第一个严格前缀最小值的位置。那么 \(u\) 处维护的三个信息都可以从 \(anc_u\) 处递推得出。求出 \(anc_u\) 可以直接倍增跳,也可以维护一个根链上的单调栈,在单调栈上二分,注意需要用 \(\log\) 的代价让单调栈可撤销。

具体实现时可以遍历 \(rt\) 的每个儿子 \(v\),先更新子树内的点的答案,再加入子树内点的贡献,正反各跑一次。

这样我们得到了 \(\mathcal{O}(n\log{n}\log{V})\) 的做法,常数很大。

可以每次分治把所有 \(\operatorname{minpre}(rt,u)\)\(\operatorname{sum}(rt,u)\) 拿出来离散化,把动态开点权值线段树改成权值 BIT。这样时间复杂度变成了 \(\mathcal{O}(n\log^2{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>;
using pil = pair<int, ll>;
const int N = 3e5 + 5;

template<typename T> inline T lowbit(T x) { return x & -x; }
template<typename T> inline void chk_min(T &x, T y) { if (y < x) x = y; }
template<typename T> inline void chk_max(T &x, T y) { if (x < y) x = y; }

int n, rt, sz[N];
int sgt1_rt, sgt2_rt;
int stmp, dfn[N], rdfn[N];
int top, stk[N], up[N];
int szd; ll d[N << 1];
ll S[N], C[N];
ll sum[N], mn_pre1[N], mn_pre2[N];
int cnt1[N], cnt2[N];
bool del[N], is_pre[N];
vector<pii> T[N];

struct BIT {
	ll c[N << 1];
	void init() { fill(c + 1, c + szd + 1, 0); }
	ll query(int x) {
		ll res = 0;
		while (x) res += c[x], x -= lowbit(x);
		return res;
	}
	void add(int x, ll v) { while (x <= szd) c[x] += v, x += lowbit(x); }
} ft1, ft2, ft3;

void dfs_rt(int x, int fx, int sz_rt) {
	sz[x] = 1;
	int mxp = 0;
	for (auto [y, z] : T[x]) if (y != fx && !del[y]) {
		dfs_rt(y, x, sz_rt);
		sz[x] += sz[y], chk_max(mxp, sz[y]);
	}
	chk_max(mxp, sz_rt - sz[x]);
	if (mxp <= sz_rt >> 1) rt = x;
}
void dfs_calc(int x, int fx) {
	sz[x] = 1, rdfn[dfn[x] = ++stmp] = x;
	int tmp = top;
	int pos = lower_bound(stk + 1, stk + top + 1, x, [&](int x, int y) {return sum[x] > sum[y];}) - stk;
	int val = stk[pos];
	up[x] = stk[pos - 1], stk[top = pos] = x;
	for (auto [y, z] : T[x]) if (y != fx && !del[y]) {
		sum[y] = sum[x] + z;
		mn_pre1[y] = min(mn_pre1[x], sum[y]);
		cnt1[y] = cnt1[x] + (is_pre[y] = sum[y] < mn_pre1[x]);
		d[++szd] = mn_pre1[y], d[++szd] = sum[y];
		dfs_calc(y, x);
		sz[x] += sz[y];
	}
	stk[pos] = val, top = tmp;
}
void dfs_solve(int x, int sz_rt) {
	dfs_rt(x, 0, sz_rt), del[x = rt] = 1;
	stmp = szd = 0;
	sum[x] = mn_pre1[x] = cnt1[x] = is_pre[x] = mn_pre2[x] = cnt2[x] = 0;
	dfs_calc(x, 0);
	sort(d + 1, d + szd + 1), szd = unique(d + 1, d + szd + 1) - d - 1;
	auto pos = [&](ll v) {return upper_bound(d + 1, d + szd + 1, v) - d - 1;};
	for (int i = dfn[x] + 1; i < dfn[x] + sz[x]; ++i) {
		int p = rdfn[i];
		S[x] += sum[p] - mn_pre1[p], C[x] += cnt1[p];
	}
	int c = 0; ll s = 0;
	auto calc_sub = [&](int y, bool flag) {
		for (int i = dfn[y]; i < dfn[y] + sz[y]; ++i) {
			int p = rdfn[i];
			if (flag) {
				if (!up[p]) mn_pre2[p] = cnt2[p] = 0;
				else {
					mn_pre2[p] = mn_pre2[up[p]] + sum[p] - sum[up[p]];
					cnt2[p] = cnt2[up[p]] + 1;
				}
				S[p] += sum[p] - mn_pre2[p], C[p] += cnt2[p];
			}
			S[p] += sum[p] * c + s;
			int tmp = pos(mn_pre2[p] - sum[p] - 1);
			int c2 = ft1.query(tmp); ll s2 = ft2.query(tmp);
			int c1 = ft1.query(szd) - c2;
			S[p] -= mn_pre2[p] * c1;
			S[p] -= sum[p] * c2 + s2;
			C[p] += (ll)cnt2[p] * c + ft3.query(tmp);
		}
	};
	auto add_sub = [&](int y) {
		for (int i = dfn[y]; i < dfn[y] + sz[y]; ++i) {
			int p = rdfn[i];
			ft1.add(pos(mn_pre1[p]), 1), ft2.add(pos(mn_pre1[p]), mn_pre1[p]);
			if (is_pre[p]) ft3.add(pos(sum[p]), sz[p]);
			++c, s += sum[p];
		}
	};
	for (auto [y, z] : T[x]) if (!del[y]) calc_sub(y, 1), add_sub(y);
	ft1.init(), ft2.init(), ft3.init();
	c = s = 0;
	for (int i = T[x].size() - 1; ~i; --i) {
		auto [y, z] = T[x][i];
		if (!del[y]) calc_sub(y, 0), add_sub(y);
	}
	ft1.init(), ft2.init(), ft3.init();
	for (auto [y, z] : T[x]) if (!del[y]) dfs_solve(y, sz[y]);
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n;
	for (int i = 1, u, v, w; i < n; ++i) {
		cin >> u >> v >> w;
		T[u].emplace_back(v, w), T[v].emplace_back(u, w);
	}
	dfs_solve(1, n);
	cout << "1\n";
	for (int i = 1; i <= n; ++i) cout << S[i] << " \n"[i == n];
	for (int i = 1; i <= n; ++i) cout << C[i] << " \n"[i == n];
	return 0;
}
posted @ 2026-02-09 12:03  P2441M  阅读(6)  评论(0)    收藏  举报