EOJ Monthly 2017.12 不见了的人口数据 (Hard)

题目链接

题意: 一棵树树上节点都有一个值ai,都条边的长度为1,现在知道每个节点的代价值
     为 all 节点t与它的距离即t节点的值积的和pi,问所有的ai的值.(0<=ai<=1000,n<=250000)

p[i]=j=1ndist(i,j)×aj,distijij. p [ i ] = ∑ j = 1 n d i s t ( i , j ) × a j , d i s t ( i , j ) 表 示 节 点 i 与 节 点 j 之 间 最 近 距 离 .

*以前一直觉得是道Guass消元题,但是n有点大,系数矩阵都不能存,更不用说O(n^3)的时间复杂度。今天看了,感觉跟以前做过的树题很相似,细细推导,发觉是道子树的和的技巧题。

*不妨记为root为1的有根树,那么sum[i]表示根为节点i的子树节点值a的和。

p[1]=i=2nsum[i]. p [ 1 ] = ∑ i = 2 n s u m [ i ] , 所 有 的 节 点 距 离 是 等 于 参 与 子 树 的 次 数 , 所 以 有 这 个 结 论 .

uvsum[1]2×sum[v]=p[v]p[u] 父 节 点 u 与 子 节 点 v 的 代 价 之 间 存 在 关 系 : s u m [ 1 ] − 2 × s u m [ v ] = p [ v ] − p [ u ]

因为v为根的子树上的所有节点(包括v)距离u的dist比距离v的dist多1,而非v子树上的点距离u的dist比距离v的dist少1,所有v的代价比u的代价多一颗子树上的值和sum[v],而少非子树上的值和(sum[1]-sum[v])。

所以我们需要做一次DFS,将所有p[son]-fa[fa]之和求出,然后联立第一个公式:得到

(n1)×sum[1]2×p[1]=all(p[son]p[fa]). ( n − 1 ) × s u m [ 1 ] − 2 × p [ 1 ] = ∑ a l l ( p [ s o n ] − p [ f a ] ) .

sum[1]即解出,在做一个dfs,解出sum[v],然后减去所有回溯的子树权值和,得到了a[v].

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
#define llt long long
using namespace std;

const int Size=25*1e4+10;
/*
  子树和 技巧题
  以rt为根的子树上所有节点的值的和为 sum[rt]
  所有
*/
vector <int> V[Size];
llt p[Size],sum[Size]={0},a[Size];//sum[1] 表示整棵以1为根的树节点总和;

//求出a1+a2+……+an 先找出所有节点代价与父节点的代价差
llt DFS(int rt,int fa){
    llt tot=0;
    for(int i=0;i<V[rt].size();++i){
        int son=V[rt][i];
        if(fa==son) continue;
        tot+=(p[son]-p[rt])+DFS(son,rt);
    }
    return tot;
}
llt dfs(int rt,int fa){

    if(rt!=1) sum[rt]=(sum[1]+p[fa]-p[rt])/2;
    a[rt]=sum[rt];
    for(int i=0;i<V[rt].size();++i){
        if(V[rt][i]==fa) continue;
        a[rt]-=dfs(V[rt][i],rt);
    }
    return sum[rt];
}
int main(){
    int n;
    scanf("%d",&n);

    for(int i=1;i<n;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        V[u].push_back(v);
        V[v].push_back(u);
    }

    for(int i=1;i<=n;++i)
        scanf("%lld",&p[i]);

    if(n==1){cout<<0<<endl;return 0;}
    sum[1]=(2*p[1]+DFS(1,0))/(n-1);
    dfs(1,0);
    printf("%lld",a[1]);
    for(int i=2;i<=n;++i)
        printf(" %lld",a[i]);
    printf("\n");

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值