【题目】
题目描述:
Star善于胡思乱想,这些天他又为带权树定义了一个叫“连通能力”的奇怪属性。对于一棵边上有权值的树( N 个结点 N-1 条边的无向连通图),我们按以下方法定义其连通能力:
1、规定某结点的代价为它到其它结点的距离(简单路径所经过边的权值和)的最大值;
2、代价最小的结点的代价作为这棵树的连通能力。
设某棵给定的树以 1 号结点为根,Star 想考察以任意结点为根的子树的连通能力有多大。请你帮助他把这 N 个值快速求出来。
输入格式:
第一行一个整数N;
接下来 N-1 行,每行三个整数 u、v、w 表示结点 u、v 间存在权值为 w 的边。
输出格式:
输出 N 行 N 个整数,第 i 行的值表示以结点 i 为根的子树所对应的连通能力。
样例数据:
输入
7
2 1 1
3 1 2
4 2 3
5 2 4
6 3 5
7 3 6输出
7
4
6
0
0
0
0
备注:
【数据范围】
对于 20% 的数据,1 ≤ N ≤ 300,所有边均与 1 号点相连;
对于 40% 的数据,1 ≤ N ≤ 300;
对于 60% 的数据,1 ≤ N ≤ 4000;
对于 80% 的数据,1 ≤ N ≤ 100000;
对于 100% 的数据,1 ≤ N ≤ 1000000、1 ≤ w ≤ 10000。
【分析】
首先,前20分是非常好得的,以第1个点为根的子树的连通能力是最大的边,以其他点为根的子树的连通能力是0
果断地写了20分的代码,然后……就没有然后了
来说一下正解吧,我们常把树的直径定义为树中最远的两个点的距离,从这个角度来说,我们把我们要求的东西定义为“树的半径”,那么,有一个结论,树的半径在树的直径上,证明如下:
那么,树的半径其中一个端点就是直径的一个端点,另一个就是离直径中点最近的一个点(这个大家感性理解一下就行了)
求树的直径参考树的直径,这里我们用树形DP来做
中点的话我们考虑用倍增来求,这样的话时间复杂度为O(N * log N),听说还有O(N)的算法但本蒟蒻不会
还有一些具体的细节就看代码吧
下面对代码中的数组进行解释:
- first,v,w,next 是用前向星存储领接表
- father 是倍增操作
- d[ i ] 表示以 i 为根的子树的直径
- r[ i ] 表示以 i 为根的子树的半径
- f1[ i ],f2[ i ] 分别表示以 i 为根的子树的最长链和次长链
- dep1[ i ],dep2[ i ] 分别表示以 i 为根的子树中最长链和次长链所到达的叶子节点
- dis[ i ] 表示 i 到根节点(也就是 1)的距离
【代码】
另外,不用 inline 的话会有一组数据 RE,本蒟蒻也不知道为什么,路过的大佬可以解释一下吗?
还有话说为什么 cout 比 printf 快啊?
附上 AC 代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define L 25
#define N 1000005
#define M 2000005
using namespace std;
int t,first[N],v[M],w[M],next[M],father[N][L];
int d[N],r[N],f1[N],f2[N],dis[N],dep1[N],dep2[N];
void add(int x,int y,int z)
{
t++;
next[t]=first[x];
first[x]=t;
v[t]=y;
w[t]=z;
}
inline void getfather(int x)
{
int i,j;
for(i=first[x];i;i=next[i])
{
j=v[i];
if(j==father[x][0])
continue;
father[j][0]=x;
getfather(j);
}
}
inline void search(int x)
{
int i,j,depth;
dep1[x]=x;
dep2[x]=x;
for(i=first[x];i;i=next[i])
{
j=v[i];
if(j==father[x][0])
continue;
dis[j]=dis[x]+w[i];
search(j);
if(r[j]>r[x])
{
d[x]=d[j];
r[x]=r[j];
}
if(f1[x]<f1[j]+w[i])
{
f2[x]=f1[x];
dep2[x]=dep1[x];
f1[x]=f1[j]+w[i];
dep1[x]=dep1[j];
}
else if(f2[x]<f1[j]+w[i])
{
f2[x]=f1[j]+w[i];
dep2[x]=dep1[j];
}
}
depth=dep1[x];
if(d[x]<f1[x]+f2[x])
{
d[x]=f1[x]+f2[x];
for(i=20;i>=0;--i)
if(depth!=x&&(f2[x]+dis[father[depth][i]]-dis[x]>=f1[x]-dis[father[depth][i]]+dis[x]))
depth=father[depth][i];
r[x]=f2[x]+dis[depth]-dis[x];
if(depth!=x&&f1[x]-dis[father[depth][0]]+dis[x]<r[x])
r[x]=f1[x]-dis[father[depth][0]]+dis[x];
}
}
int main()
{
// freopen("connect.in","r",stdin);
// freopen("connect.out","w",stdout);
int n,i,j,x,y,z;
scanf("%d",&n);
for(i=1;i<n;++i)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
getfather(1);
for(j=1;j<=20;++j)
for(i=1;i<=n;++i)
father[i][j]=father[father[i][j-1]][j-1];
search(1);
for(i=1;i<=n;++i)
cout<<r[i]<<'\n';
// fclose(stdin);
// fclose(stdout);
return 0;
}