[NOI2023] 贸易 题解

[NOI2023] 贸易 题解

link

Description

给定一个 \(2^n-1\) 个节点的完美二叉树,其中每条边均为单向边,方向均为儿子连向祖先。

此外,给定 \(m\) 条前向边,即由祖先连向其后代的单向边。

树边与前向边均有对应的长度 \(w\)。记 \(dis(u,v)\) 表示 \(u,v\) 两点间在给定图中的最短路径长度,若 \(u,v\) 不连通或 \(u=v\) ,则记 \(dis(u,v) = 0\)。求 \(\sum\limits_{u=1}^{2^n-1} \sum\limits_{v=1}^{2^n-1} dis(u,v)\) 的值。

\(2 \le n \le 18, 1 \le m \le 2^n\)。

Solution

此题有 40 pts Floyd 暴力。我们来思考正解。

image-20261030084040313

考虑把贡献挂到一个点上,即对于点 \(u\),计算所有点到它的最短路之和。

对于 \(u\) 子树内的点,能且只能走树边到达 \(u\),这个贡献可以一次树上 DP 算出来。

对于 \(u\) 子树外的点,一定是先走树边到达 \(u\) 父链上的某点,然后走前向边到达 \(u\),即如下图所示的结构。

image-20261030084040313

假设当前枚举到 \(u\) 的祖先为 \(t\),设 \(s\) 为子树内不包含 \(u\) 的 \(t\) 的子节点,那么此时的贡献为

\[F + siz_{s} \times dis(t,u) \]

其中 \(F\) 表示 \(s\) 子树内所有点到 \(t\) 的最短路之和,就是走树边,刚才已经求过了。现在考虑求 \(t\) 到 \(u\) 的最短路。

考虑树上从上往下 \(dp\),在计算 \(dis(t,u)\) 时,\(dis(fa_t,u)\) 已经计算完了,那么在 \(fa_t\) 子树内跑一边 Dijkstra 即可。至于优化,可以设初始值 \(dis(t,u) = dis(fa_t,u) + w(t,fa_t)\),然后再松弛。注意松弛时需要分别考虑树内边和前向边。

注意跳祖先的时候需要判断可达性,如果某个祖先不可达,那么祖先的祖先仍然不可达。

树高为 \(n\),每个点最多 \(n\) 次最短路,时间复杂度 \(O(n^2 2^n)\)。

Code

#include <bits/stdc++.h>
#define int long long
#define inf 1e18
using namespace std;
const int N = 3e5 + 5, mod = 998244353;
int n, m, a[N], siz[N], dfn[N], curDfn, id[N], bnd[N], dep[N], f[N], g[N], dis[N][25], ans;
struct Edge {
	int v, w;
	friend bool operator < (Edge x, Edge y) {
		return x.w > y.w;
	}
};
vector<Edge> G[N];
bitset<N> vis;
void Dijkstra(int s, int depth) {
	int fa = (s >> 1);
	for (int i = dfn[s]; i <= bnd[s]; i++) {
		vis[id[i]] = 0;
		dis[id[i]][depth] = min(dis[id[i]][dep[fa]] + a[s], (int)inf);
	}
	priority_queue<Edge> q;
	q.push({s, 0});
	dis[s][depth] = 0;
	while (not q.empty()) {
		int u = q.top().v; q.pop();
		if (vis[u]) continue;
		vis[u] = 1;
		for (auto tmp : G[u]) {
			int v = tmp.v, w = tmp.w;
			if (dis[v][depth] > dis[u][depth] + w) {
				dis[v][depth] = dis[u][depth] + w;
				q.push({v, dis[v][depth]});
			}
		}
		if (u != s) {
			int v = (u >> 1), w = a[u];
			if (dis[v][depth] > dis[u][depth] + w) {
				dis[v][depth] = dis[u][depth] + w;
				q.push({v, dis[v][depth]});
			}
		}
	}
}
#define lson (u << 1)
#define rson (u << 1 | 1)
#define fa (u >> 1)
void DFS1(int u) {
	siz[u] = 1;
	dfn[u] = ++curDfn;
	id[curDfn] = u;
	dep[u] = dep[fa] + 1;
	if (lson <= n) {
		DFS1(lson);
		siz[u] += siz[lson];
		f[u] = (f[u] + f[lson] + siz[lson] * a[lson] % mod) % mod;
	}
	if (rson <= n) {
		DFS1(rson);
		siz[u] += siz[rson];
		f[u] = (f[u] + f[rson] + siz[rson] * a[rson] % mod) % mod;
	}
	bnd[u] = curDfn; // Reverse
}
void DFS2(int u) {
	int t = u;
	while (t != 1) {
		if (dis[u][dep[t >> 1]] == inf) break;
		g[u] = (g[u] + (f[t >> 1] - f[t] + mod - siz[t] * a[t] % mod + mod) % mod + (siz[t >> 1] - siz[t]) * dis[u][dep[t >> 1]] % mod) % mod;
		t >>= 1;
	}
	Dijkstra(u, dep[u]);
	if (lson <= n) {
		DFS2(lson);
	}
	if (rson <= n) {
		DFS2(rson);
	}
}
signed main() {
	cin.tie(0) -> sync_with_stdio(0);
	cin >> n >> m;
	n = (1 << n) - 1;
	for (int i = 2; i <= n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; i++) {
		int u, v, w; cin >> u >> v >> w;
		G[u].push_back({v, w});
	}
	DFS1(1);
	for (int i = 1; i <= n; i++) {
		dis[i][0] = inf;
	}
	DFS2(1);
	for (int i = 1; i <= n; i++) {
		ans = (ans + f[i] + g[i]) % mod;
	}
	cout << ans << '\n';
	return 0;
}
posted @ 2026-10-30 08:56  L-Coding  阅读(3)  评论(0)    收藏  举报