250602 模拟赛 T1 题解
题意
给定一棵 \(n\) 个节点的无根树,边有边权。记 \(dis(u,v)\) 表示 \(u\) 和 \(v\) 在树上的距离,\(f(x,u,v)\) 表示 \(u,v\) 简单路径上离 \(x\) 最近的一个点到 \(v\) 的距离。
给定常数 \(k\),求有多少有序对 \((u,v)\) 满足
\[
\frac 1n\sum_{x=1}^nf(x,u,v)\ge \frac 12 dis(u,v)+k
\]
其中 \(n\le 10^6\)
题解
在固定 \(u,v\) 的情况下,记 \(y_x\) 表示 \(u,v\) 简单路径上离 \(x\) 最近的一个点。我们将题目给定的约束中不够直观、复杂的东西变形,得到:
\[
2\sum_{x=1}^{n}dis(y_x,v)\ge n\times dis(u,v)+2nk
\]
注意到 \(\sum dis(y_x,v)\) 是路径上每个节点挂的子树大小的加权和,比较容易计算;这里可以使用点分治做到 \(O(n\log^2n)\),获得 \(75pts\)。
我们发现,直接通过这个式子似乎无法获得更低的时间复杂度。因此尝试继续变形:
\[
\begin{align*}
\sum_{x=1}^n2dis(y_x,v)-dis(u,v)\ge&\ 2nk\\
\sum_{x=1}^ndis(y_x,v)-dis(y_x,u)\ge &\ 2nk\\
\end{align*}
\]
式子的形式似乎又变好了一点,但是没有产生任何本质的变化。注意到 \(y_x\) 会让式子变得难以计算,因为它和 \(u,v,x\) 同时有关。注意到我们可以加上 \(dis(x,y_x)\) 再减去 \(dis(x,y_x)\):
\[
\begin{align*}
\sum_{x=1}^n dis(x,v)-dis(x,u)\ge&\ 2nk\\
\sum_{x=1}^n dis(x,v)-\sum_{x=1}^n dis(x,u)\ge&\ 2nk
\end{align*}
\]
其中 \(\sum dis(x,u)\) 是只和 \(u\) 有关的常量。我们先求出所有 \(s_u=\sum dis(x,u)\),排序后双指针即可做到 \(O(n\log n)\)。
AC 代码
| #include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N = 1e6 + 10;
struct Edge {
int v, w, next;
} pool[2 * N];
int ne, head[N];
void addEdge(int u, int v, int w) {
pool[++ne] = {v, w, head[u]};
head[u] = ne;
}
int T;
int n, k;
ll ans;
void clear() {
for(int i = 1; i <= n; i++) head[i] = 0;
ne = 0;
ans = 0;
}
ll sz[N], sum[N];
ll dfs1(int u, int fa) {
ll res = 0;
sz[u] = 1;
for(int e = head[u]; e; e = pool[e].next) {
int v = pool[e].v, w = pool[e].w;
if(v == fa) continue;
res += dfs1(v, u);
sz[u] += sz[v];
res += sz[v] * w;
}
return res;
}
void dfs2(int u, int fa) {
for(int e = head[u]; e; e = pool[e].next) {
int v = pool[e].v, w = pool[e].w;
if(v == fa) continue;
sum[v] = sum[u] + (n - 2 * sz[v]) * w;
dfs2(v, u);
}
}
int main() {
cin >> T;
while(T--) {
cin >> n >> k;
clear();
for(int i = 1; i <= n - 1; i++) {
int u, v, w;
cin >> u >> v >> w;
addEdge(u, v, w);
addEdge(v, u, w);
}
sum[1] = dfs1(1, 0);
dfs2(1, 0);
sort(sum + 1, sum + 1 + n);
for(int i = 1, j = 1; i <= n; i++) {
while(sum[i] - sum[j + 1] >= 2ll * n * k) ++j;
if(sum[i] - sum[j] >= 2ll * n * k) {
ans += j;
}
}
cout << ans << '\n';
}
return 0;
}
|