在一起(换根DP)

ACMer分布在全国的n个不同的城市,编号为1~n。任意两个城市都连通,且只有唯一的一条通路。你们决定来一次全体聚会,请选出一个城市作为聚会地,使得所有人的距离和最小。

Input

第一行是一个整数T (1 ≤ T ≤ 10),表示样例的个数,所有测试数据样例总数不超过100。

每个样例的第一行是一个整数n (1 ≤ n ≤ 10000)。

第二行是n个整数,表示第i个城市里的ACMer的人数ai (1 ≤ ai ≤ 100)。

从第三行起的n - 1行,每行三个整数u, v, w,  (1 ≤ u < v ≤ n), 1 ≤ w ≤ 100,表示城市uv之间有一条路,距离为w

Output

依次输出每个样例的结果,为两个整数,第一个为聚会城市的编号,第二个为所有人的距离和,两者之间用一个空格隔开。

如果存在多个符合条件的城市,取编号最小的。

Example

Input

Copy

1
6
1 2 3 4 5 6
1 2 1
1 3 1
2 4 1
2 5 1
3 6 1

Output

Copy

2 34

Note

选1号城市作为聚会地,距离和为(2 + 3)·1 + (4 + 5 + 6)·2 = 35。

选2号城市作为聚会地,距离和为(1 + 4 + 5)·1 + 3·2 + 6·3 = 34。

选3号城市作为聚会地,距离和为(1 + 7)·1 + 2·2 + (4 + 5)·3 = 39。

选4号城市作为聚会地,距离和为2·1 + (1 + 5)·2 + 3·3 + 6·4 = 47。

选5号城市作为聚会地,距离和为2·1 + (1 + 4)·2 + 3·3 + 6·4 = 45。

选6号城市作为聚会地,距离和为3·1 + 1·2 + 2·3 + (4 + 5)·4 = 47。

        这是一道典型的换根DP,对于换根DP我们一般使用两次DFS,先任选一个节点作为根节点,树型DP搜索出每个子树的节点数(也就是人数),以及每个子树到根的代价,对于这题就是点与点的距离乘上人数。

        第二次DFS进行换根,让孩子节点作为根与父节点为根的关系,对于本题,如果子节点变成根,那么子节点对应的子树上的人数到达根的代价都会减少 Wuv(边权),而父节点一边对应的代价便都会加大 Wuv;表达式就是:distv = distu - sumv*Wuv + (sum - sumv)*Wuv;最后遍历每个点找出最小代价。

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
//#define int long long
const long long INF = 0x3f3f3f3f3f3f;
struct node{
    int to;
    int nxt;
    int w;
}e[20010];
long long cnt,ans = INF;
int head[10010],a[10010],dist[10010];
long long s = 0,sum[10010],f[10010];
void add(int u,int v,int w)
{
    e[++cnt].nxt = head[u];
    head[u] = cnt;
    e[cnt].to = v;
    e[cnt].w = w;
}
void dfs1(int u,int fa)
{
    sum[u] = a[u];
    for(int i = head[u];i;i = e[i].nxt)
    {
        int v = e[i].to;
        if(v == fa)
        {
            continue;
        }
        
        dist[v] = dist[u] + e[i].w;//求出节点距离
        
        dfs1(v,u);
        sum[u] += sum[v];//预处理每个节点对应的子树的人数之和
    }
}
void dfs2(int u,int fa)
{
    for(int i = head[u];i;i = e[i].nxt)
    {
        int v = e[i].to;
        if(v == fa) continue;
        f[v] = f[u] - 1ll*sum[v]*e[i].w + 1ll*(s-sum[v])*e[i].w;//以v为根的代价和父节点为根的代价关系
        dfs2(v,u);
    }
}
signed main()
{
    ios::sync_with_stdio(false),cin.tie(0);
    int tt;
    cin >> tt;
    while(tt--)
    {
        s = 0;ans = INF;//注意初始化
        int n;
        cin >> n;
        cnt = 0;
        for(int i = 1;i <= n;i++)
        {
            dist[i] = 0;
            f[i] = 0;
            sum[i] = 0;
            head[i] = 0;
            cin >> a[i];
            s += a[i];
        }
        for(int i = 1;i < n;i++)
        {
            int a,b,c;
            cin >> a >> b >> c;
            add(a,b,c);add(b,a,c);
        }
        dfs1(1,1);
        for(int i = 1;i <= n;i++)
        {
            f[1] += dist[i]*a[i];//以1为根的代价
        }
        dfs2(1,1);
        int k = 0;
        for(int i = 1;i <= n;i++)
        {
            if(f[i] < ans)
            {
                ans = f[i];
                k = i;
            }
        }
        cout << k << ' ' << ans << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值