[计蒜客]蒜头君的树

题目描述

蒜头君有一棵有根树,树的每一边都有边权,蒜头君想知道任意两点间最短距离之和为多少。另外,由于各种原因,蒜头君的树的边的边权会发生若干次改变,蒜头君想让你告诉他,每一次改变后,任意两点间最短距离之和为多少?

输入格式

第一行一个正整数n,表示蒜头君的树上的结点个数。

接下来n-1行,每行两个正整数$$x_i,y_i$$$$x_i$$​​表示$$i+1$$结点的父亲结点的编号,保证其父结点编号小于自己编号。$$y_i$$​​表示$$i+1$$号结点的父亲结点与自己间边的距离。

接下来一行一个整数m,表示树的边权发生改变的次数。

接下来m行,每行两个正整数a,b表示将a结点与其父亲结点之间的距离改为b,保证$$a \geq 2$$。

输出格式

先输出一个整数,表示对于原始的树任意两点间最短距离之和。

接下来m行,每行输出一个整数,表示每一次改变后,任意两点间最短距离之和。

数据规模

样例输入

4
1 1
1 1
1 1
1
2 2

样例输出

9
12

思路

dfs

$$最短距离和= \sum(树上每一条边被最短路经过的次数 \times 这条边的长度)$$

$$一个节点到它父节点的边被经过的次数=该节点以及它的子孙的节点个数 \times 除了该节点和它子孙之外的所有节点总个数$$

每一个节点以及它子孙节点的个数总和用一遍dfs保存在num数组中,然后$$ans+= num[i] \cdot (n - num[i])$$,就可以求出在没有进行任何操作时的最短距离和。

对于每一次操作将第x个节点到它父节点的边长由原来的a[x]改为y,则将原来的最短距离和$$ans+=(y-a[x])(num[x])(n-num[x])$$,同时将a[x]改为y即可。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAXNUM=1e6+10;
ll a[MAXNUM];
ll n,m;
ll num[MAXNUM];
vector<int>E[MAXNUM];

void dfs(int x)
{
    num[x]=1;
    for(int i=0;i<E[x].size();i++)
    {
        int v=E[x][i];
        dfs(v);
        num[x]+=num[v];
    }
}

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

int main()
{
    n=read();
    for(int i=2;i<=n;i++)
    {
        int x;
        x=read();a[i]=read();
        E[x].push_back(i);
    }
    dfs(1);
    ll ans=0;
    for(int i=2;i<=n;i++)
    {
        ans+=a[i]*(ll)(num[i])*(ll)(n-num[i]);
    }
    printf("%lld\n",ans);
    m=read();
    while(m--)
    {
        ll x=read(),y=read();
        ans+=(y-a[x])*(ll)(num[x])*(ll)(n-num[x]);
        printf("%lld\n",ans);
        a[x]=y;
    }
}

results matching ""

    No results matching ""