P1043 · OFFICIAL SOLUTION

P1043 策划道路 官方题解

Gioush OJ · P1043 策划道路

策划道路

【题意简述】

给出一棵 nn 个结点的带权树。每个方案独立地新增结点 n+1n+1,并用权值为 xx 的边连接到原结点 kk,求新树中全部有序点对的距离和。

【Hint】

【提示】

设原树全部有序点对距离和为 SS,结点 kk 到全部原结点的距离和为 DkD_k。方案答案为 S+2(Dk+nx)S+2(D_k+nx),只需换根求出全部 DkD_k

【数据点 1∼31\sim 31∼3】

对每份方案直接建出新树,再从每个结点出发搜索。时间复杂度为 O(qn2)\mathcal{O}(qn^2)

【数据点 4∼74\sim 74∼7】

记原树全部有序点对距离和为 SS,并定义

Dk=v=1ncost(k,v).D_k=\sum_{v=1}^{n}\operatorname{cost}(k,v).

新结点到原结点 vv 的距离为 x+cost(k,v)x+\operatorname{cost}(k,v),反向距离相同,因此一份方案的答案为

S+2(Dk+nx).S+2(D_k+nx).

固定根后,边 (u,v)(u,v) 两侧分别有 Size(v)\operatorname{Size}(v)nSize(v)n-\operatorname{Size}(v) 个结点,其对 SS 的贡献为

2Size(v)(nSize(v))wu,v.2\operatorname{Size}(v)\bigl(n-\operatorname{Size}(v)\bigr)w_{u,v}.

这一档可以预处理 SS,再对每个方案搜索求 DkD_k

【正解】

任选结点 11 为根。令 Size(u)\operatorname{Size}(u) 表示 uu 的子树大小,令 FuF_u 表示 uu 到其子树内全部结点的距离和。若 vv 枚举 uu 的儿子,则

Size(u)=1+vSize(v),Fu=v(Fv+Size(v)wu,v).\begin{aligned} \operatorname{Size}(u)&=1+\sum_v\operatorname{Size}(v),\\ F_u&=\sum_v\left(F_v+\operatorname{Size}(v)w_{u,v}\right). \end{aligned}

所以 D1=F1D_1=F_1

接下来换根。设 vvuu 的儿子,边权为 ww。根从 uu 移到 vv 后,vv 子树内的 Size(v)\operatorname{Size}(v) 个结点距离均减少 ww,其余结点距离均增加 ww,所以

Dv=DuSize(v)w+(nSize(v))w=Du+(n2Size(v))w.\begin{aligned} D_v &=D_u-\operatorname{Size}(v)w +\bigl(n-\operatorname{Size}(v)\bigr)w\\ &=D_u+\bigl(n-2\operatorname{Size}(v)\bigr)w. \end{aligned}

第二次搜索即可求出全部 DuD_u。又因为每个有序点对 (u,v)(u,v) 恰好在 DuD_u 中出现一次,原树距离和为

S=u=1nDu.S=\sum_{u=1}^{n}D_u.

最后对每份方案输出 S+2(Dk+nx)S+2(D_k+nx)

【复杂度分析】

预处理时间复杂度为 O(n)\mathcal{O}(n),每次询问为 O(1)\mathcal{O}(1),总时间复杂度为 O(n+q)\mathcal{O}(n+q),空间复杂度为 O(n)\mathcal{O}(n)

【参考代码】

#include <cstdio>#include <cstring>#define MAXN 200010const int Mod=998244353;using namespace std;int Calc(int a,int b){return (a+=b)<Mod?a:a-Mod;}int Mul(int a,int b){return 1ll*a*b%Mod;}int Del(int a,int b){return (a-=b)>0?a:a+Mod;}int Total=0,Head[MAXN],Count[MAXN],Root=1,Size[MAXN],n;int Sum=0;struct E{    int St,Ed;    int Next;    int Value;}Edge[MAXN<<1];void Edge_Add(int St,int Ed,int Value){    Total++;    Edge[Total].St=St;    Edge[Total].Ed=Ed;    Edge[Total].Value=Value;    Edge[Total].Next=Head[St];    Head[St]=Total;}void DFS_F(int Now,int From,int Value){    Size[Now]=1;    for(int i=Head[Now];i;i=Edge[i].Next){        int To=Edge[i].Ed;        if(To==From) continue;        DFS_F(To,Now,Calc(Value,Edge[i].Value));        Size[Now]+=Size[To];        Count[Root]=Calc(Count[Root],Calc(Value,Edge[i].Value));    }}void DFS_Pass(int Now,int From){    for(int i=Head[Now];i;i=Edge[i].Next){        int To=Edge[i].Ed;        if(To==From) continue;        Count[To]=Calc(Count[Now],Mul((n-Size[To]-Size[To]),Edge[i].Value));        Count[To]=(Count[To]%Mod+Mod)%Mod;        DFS_Pass(To,Now);    }}void Build(int Line,int Cost){    printf("%d\n",Calc(Sum,Mul(2,Calc(Count[Line],Mul(Cost,n)))));}void Solve(){    int Q;    scanf("%d%d",&n,&Q);    memset(Head,0,sizeof(int)*(n+2));    memset(Count,0,sizeof(int)*(n+2));    memset(Size,0,sizeof(int)*(n+2));    Total=Sum=0;Root=1;    int In1,In2,In3;    for(int i=1;i<=n-1;i++){        scanf("%d%d%d",&In1,&In2,&In3);        Edge_Add(In1,In2,In3);        Edge_Add(In2,In1,In3);    }    DFS_F(Root,Root,0);    DFS_Pass(Root,Root);    for(int i=1;i<=n;i++) Sum=Calc(Sum,Count[i]);    while(Q--){        scanf("%d%d",&In1,&In2);        Build(In1,In2);    }}int main(){    int c,T;    scanf("%d%d",&c,&T);    while(T-->0) Solve();    return 0;}