洛谷链接:https://www.luogu.com.cn/problem/P2052
我们先思考一下怎么表达题意
题目要求的是求出当前点和其他点连线之间的特殊权值之和
这种特殊权值就是左边点的集合和右边点的集合差的绝对值*边的权值(abs(size[u]-size[v])*w[i])
这种很容易让人想到统计每个节点下面的子树
也确实需要统计每个节点下面的子树
但是我们知道刚开始我们是任意选一个点去遍历的,也就是说指不定哪个点作为根节点,那么我们就需要想如何通过一次两次的dfs来得到我们想要的答案
因为n^2的做法是行不通的
我们来实践一下看思维难度主要在哪里:
我们先选一个不那么特殊的点,2点
2点刚开始的size肯定是1,那么在遍历到周边5点的时候肯定要满足一种情况:
也就是5作为一个孤立的点和2点连接,那么2点在和5点连接时的size实际上是n!
这时候我们就要考虑了,是刚开始就预处理好每个点下面的子树的大小吗?
我们可以沿着这个思路往下想,看能不能保证任意一个点作为根节点得到的结果都是一样的
当我们把2点作为根节点,遍历到5点和遍历到1点的时候,情况都是可控的
但是1点往下呢?
你会发现1点下面有3个分叉!
也就是说当1点往下遍历的时候若是遍历到4点,那么size[4] = 1,显然4点的处境和5点是一样的,这时候size[1]反而是n
这样可行吗?
我们应该换思路了
也就是说每个点和周围点连接的size之差其实是和它的父节点没什么关系的!只和它自己本身的子树大小有关系啊!
这同时也启示我们:即使是要表达子节点size大小和父节点size大小,也不用一定就要扯上关系
那么,我们要不要维护size这个数组要维护?
当然你要维护,而且是一边维护一边统计值并累加
为什么就不能先统计,之后再累加呢?
也可以,不过就是两次dfs罢了
这个题的思想就是让我们求出每个点的子树,仅此而已
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N = 2e6+10; //ll和两倍点和数据范围需要特别注意!!!
ll e[N],ne[N],h[N],w[N],size[N],idx;
ll n;
ll value;
void add(int a,int b,int c)
{
e[idx] = b,ne[idx] = h[a],w[idx] = c,h[a] = idx++;
}
void dfs(int u,int fa)
{
size[u] = 1;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==fa) continue;
dfs(j,u);
size[u] += size[j];
value += (ll)(w[i]*abs(2*size[j]-n));
}
}
/*void cal(int u,int fa)
{
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==fa) continue;
dfs(j,u);
value += (ll)(w[i]*abs(2*size[j]-n));
}
}*/
int main()
{
memset(h,-1,sizeof h);
cin>>n;
for(int i=1;i<=n-1;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
dfs(1,0);
//cal(1,0);
cout<<value;
return 0;
}
要加油啊!!!