【codevs1419】藤原妹红 树形DP

题目描述 Description

在幻想乡,藤原妹红是拥有不老不死能力的人类。虽然不喜欢与人们交流,妹红仍然保护着误入迷途竹林村民。由于算得上是幻想乡最强的人类,对于她而言,迷途竹林的单向道路亦可以逆行。在妹红眼中,迷途竹林可以视为一个由N个路口(编号1..N),M条不同长度双向路连接的区域。妹红所在的红之自警队为了方便在迷途竹林中行动,绘制了一张特殊的迷途竹林地图,这张地图上只保留了N-1条道路,这些道路保证了任意两个路口间有且仅有一条路径,并且满足所有保留的道路长度之和最小,我们称这些道路为『自警队道路』。现在妹红打算在其中一个连接有多条『自警队道路』的路口设立根据地,当去掉根据地这个根据地所在路口后,就会出现某些路口间无法通过『自警队道路』相互连通的情况,我们认为这时仍然能够通过『自警队道路』连通的路口属于同一个『区域』。妹红希望最后每个『区域』的『自警队道路』总长尽可能平均,请计算出她应该选择哪一个路口作为根据地。
  下例中红色的路口为妹红选择的根据地,实线边表示『自警队道路』,绿色虚线边表示非『自警队道路』,数字表示边权,『自警队道路』中相同颜色的实线边代表属于同一个『区域』:
  
  (尽可能平均即权重最小,设每一块『区域』的路线总长为Length[i],平均路线长度为Avg=SUM{Length[i]}/区域数,权重d=∑( (Length[i]-Avg)^2 ) )

输入描述 Input Description

第1行:2个正整数N,M
  第2..M+1行:每行2个整数u,v和1个实数len,表示u,v之间存在长度为len的边

输出描述 Output Description

第1行:1个整数,最后选择的路口编号,存在多个可选路口时选择编号小的

样例输入 Sample Input

3 3
3 1 5
3 2 4
1 2 3

样例输出 Sample Output

2

数据范围及提示 Data Size & Hint

对于60%的数据:3 ≤ N ≤ 2,000,N-1 ≤ M ≤ 50,000
  对于100%的数据:3 ≤ N ≤ 40,000,N-1 ≤ M ≤ 200,000
  对于100%的数据:0 < len ≤ 100,000,000
 提示
  样例解释:
  妹红的『自警队道路』为(1,2)和(2,3)。
  只能选择2作为根据地,产生的两个区域Length[i]分别为3和4。
  所以方差为:(4-3.5)^2 + (3-3.5)^2 = 0.5

  注意:
  保证不存在相同距离的线路,两个路口间可能出现多条路径,且任意点对间至少存在一条路径。


本来想是树上最长链的中点,即树的中心…但其实并不是这样,因为要求方差最小,而断开某个点所分成的份数是不一样的,并且树的中心也不一定只有一个,所以就可以排除。

这样就可以树形DP了,思想很简单,按公式来就好。

务必注意是double类型!!!

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int SZ = 3000010;
const double INF = 1e30;

int n,m;

int head[SZ],nxt[SZ],tot = 0,ans,num[SZ];

struct edge{
    int t;
    double d;
}l[SZ];

void build(int f,int t,double d)
{
    l[++ tot].t = t;
    l[tot].d = d;
    nxt[tot] = head[f];
    head[f] = tot;
}

double dist[SZ],sum = 0;

double maxd = INF;

double dfs(int u,int fa)
{
    double avg = sum/(double)num[u];
    double d = 0,son = 0;
    for(int i = head[u];i;i = nxt[i])
    {
        int v = l[i].t;
        if(v == fa) continue;
        dist[v] = l[i].d + dfs(v,u);
        d += (dist[v] - avg) * (dist[v] - avg);
        son += dist[v];
    }
    d += (sum - son - avg) * (sum - son - avg);
//  cout<<u<<" "<<avg<<" "<<d<<" "<<son<<endl;
    if(num[u] != 1)
        if(d < maxd || (d == maxd && ans > u)) ans = u,maxd = d;

    return son;
}



struct haha{
    int f,t;
    double d;
}e[SZ];

int fa[SZ];

int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

bool cmp(haha a,haha b) { return a.d < b.d; }

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++) fa[i] = i;
    for(int i = 1;i <= m;i ++)
    {
        scanf("%d%d%lf",&e[i].f,&e[i].t,&e[i].d);
    }
    sort(e + 1,e + 1 + m,cmp);

    for(int i = 1;i <= m;i ++)
    {
        int x = find(e[i].f);
        int y = find(e[i].t);
        if(x != y) 
        {
            fa[x] = y;
            build(e[i].f,e[i].t,e[i].d);    build(e[i].t,e[i].f,e[i].d);
            num[e[i].f] ++;     num[e[i].t] ++;
            sum += e[i].d;
        }
    }
    dfs(1,0);

    printf("%d",ans);
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值