NOIP 模拟赛 单 single

题面:
这里写图片描述

样例输入 Sample Input:

2
2
1 2
1
17 31
2
1 2
0
31 17

样例输出 Sample Output:

31 17
17 31

数据范围:
这里写图片描述

分析:
觉得这道题特别巧啊,考试的时候只考虑到了如果O(n)如何从a推b,从b推a简直无力。

我们假定以1为根,假设两个节点u,v,u与v之间有一条边相连。
从a推b:
b[u]=dis(1,u)*a[1]+dis(2,u)*a[2]+…+dis(n,u)*a[n];
b[v]=dis(1,v)*a[1]+dis(2,v)*a[2]+…+dis(n,v)*a[n];
所以不难得到:
b[v]-b[u]=(sum-siz[v])-siz[v]
//siz[v]表示v的子树的a的和,sum表示所有节点的a的和
因为对于v的子树,v到它们各点的距离比u小1,所以b[v]就比b[u]总共小siz[v],而对于子树外的点,则v到它们各点的距离比u大1,所以b[v]就比b[u]总共大sum-siz[v]
暴力求出b[1],再dfs一次求出每个节点

从b推a:
同样的定义sum和siz,
b[v]-b[u]=(sum-siz[v])-siz[v];
我们可以对于除根节点外的每一个节点都进行这样的相减,最后的和就可以得到
(n-1)sum-2(siz(1)+siz(2)+…+siz(n));
又因为b[1]=siz(2)+siz(3)+siz(4)+… +siz(n)
//这一步稍微反应下还是很容易的
//虽然我之前没想到
所以我们用上面得到的式子加2*b[1],就得到了(n-1)倍的sum,再将sum代入,利用dfs(由于叶节点的siz[v]=a[v],而非叶节点的a即sum-(子树中除它以外的a的和)),就可以得到a[i]了

没有什么很难的算法,但是很巧。。

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;

const int N = 1e5 + 10;

inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int n;
struct node{int pre,v;} e[N<<1];
int num=0,head[N];
void addedge(int from,int to){
    e[++num].pre=head[from],head[from]=num;
    e[num].v=to;
}

int T;
ll a[N],b[N],sum=0;
#define ms(x,y) memset(x,y,sizeof(x))
void zero(){
    ms(head,0);num=0;ms(b,0);ms(a,0);sum=0;
}

int dep[N];
ll sz[N];
void dfs_first1(int u,int f){
    sz[u]=a[u];
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].v;
        if(v==f) continue;
        dep[v]=dep[u]+1;
        dfs_first1(v,u);
        sz[u]+=sz[v];
    }
}

void dfs_first2(int u,int f){
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].v;
        if(v==f) continue;
        //int ans=query(1,1,n,in[v],out[v]);
        b[v]=b[u]-sz[v]+sum-sz[v];
        dfs_first2(v,u);
    }
}

ll delta[N];
void dfs_second1(int u,int f){
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].v;
        if(v==f) continue;
        delta[v]=b[v]-b[u];
        dfs_second1(v,u);
    }
}

void dfs_second2(int u,int f){
    for(int i=head[u];i;i=e[i].pre){
        int v=e[i].v;
        if(v==f) continue;
        a[v]=(sum+b[u]-b[v])/2;
        a[u]-=a[v];
        dfs_second2(v,u);
    }
}

int main(){
    freopen("single.in","r",stdin);
    freopen("single.out","w",stdout);
    T=read();
    while(T--){
       zero();
       n=read();
       for(int i=1;i<n;i++){
           int u=read(),v=read();
           addedge(u,v);addedge(v,u);
       }
       int opt=read();
       if(opt==0){
          for(register int i=1;i<=n;i++) a[i]=read(),sum+=a[i];
          dep[1]=0;
          dfs_first1(1,0);
          for(register int i=2;i<=n;i++) b[1]+=a[i]*dep[i];
          dfs_first2(1,0);
          for(register int i=1;i<=n;i++) printf("%lld ",b[i]);
          printf("\n");
       }
       else{
          for(register int i=1;i<=n;i++) b[i]=read();
          dfs_second1(1,0);
          for(register int i=2;i<=n;i++) sum+=delta[i];
          sum+=2*b[1];sum/=(n-1);a[1]=sum;//printf("%d\n",sum);
          dfs_second2(1,0);
          for(register int i=1;i<=n;i++) printf("%lld ",a[i]);
          printf("\n");
       }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值