G 树上求和
https://ac.nowcoder.com/acm/contest/4784/G
题解:
计算每条边在答案中被加了多少次,然后把这个次数升序排序,从前往后依次分配权值
n
−
1
,
n
−
2
,
n
−
3
,
…
,
3
,
2
,
1
n-1,n-2,n-3,\dots,3,2,1
n−1,n−2,n−3,…,3,2,1。
计算方法,首先dfs
计算出每个结点的子树的结点个数(包括此结点),然后
(
n
−
i
)
∗
s
i
z
e
[
i
]
(n-i)*size[i]
(n−i)∗size[i] 就是每个点对应的边在答案中被加的次数,这也是一个组合问题,把
i
i
i 点分成子树(我们叫
i
i
i 下面的点)和其他点(我们叫
i
i
i 上面的点)两个部分,那么所有的包括
(
i
,
f
a
)
(i,fa)
(i,fa) 这条边的树链个数就是从上面和下面的点中任选一个组合起来。
dfs处理一棵树的子树大小的复杂度是边数级别
O
(
n
)
O(n)
O(n)。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
struct edge
{
int before, to;
} e[maxn << 1];
int head[maxn], cnt;
int sz[maxn]; //结点i的子树(包括i点)的结点个数
void init(int N)
{
for (int i = 1; i <= N; i++)
head[i] = -1;
cnt = 0;
}
void add(int u, int v)
{
e[cnt].before = head[u]; //起点为u连的上一条边是编号为head[u]的边
e[cnt].to = v; //这条边的终点连的是v
head[u] = cnt; //起点是u的这条边是第cnt条边
cnt++;
}
void dfs(int cur, int pre) //求每个点的子树大小(结点个数)
{
sz[cur] = 1;
for (int i = head[cur]; ~i; i = e[i].before)
{
int v = e[i].to;
if (v != pre)
{
dfs(v, cur);
sz[cur] += sz[v];
}
}
}
int main()
{
int n, u, v;
scanf("%d", &n);
init(n);
for (int i = 1; i < n; i++)
{
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
dfs(1, 0);
vector<ll> sum; //每条边在答案中被加的总次数
for (int i = 1; i <= n; i++)
sum.emplace_back(sz[i] * 1LL * (n - sz[i]));
sort(sum.begin(), sum.end());
ll ans = 0;
for (int i = 1; i < n; i++)
ans += 1LL * (n - i) * sum[i];
printf("%lld\n", ans);
return 0;
}