題目大意
有一棵有\(n\)(\(n\leq10^5\))個點的樹,有點權\(d_i\)、邊權\(c_i\),有\(m\)(\(m\leq10^5\))次操作。
每次操作給定\(x,y(y\in[-1000,1000])\),將點\(x\)的點權+y,要找到一個點,使所有點的 點權乘到這個點的距離 之和最小,輸出最小的和。
題解
這題相當於求帶權重心。設點\(x\)的子樹點權和為\(s_x\),子樹內所有點到它的距離和為\(S_x\),點1為根。
有一種\(\Theta(n)\)求帶權重心的方法:
先暫時地假設根就是“重心”,考慮能不能改變“重心”使所有點\((點權)\times(到這個點的距離)\)最小:對於一個兒子\(x\),假設根到它的距離是\(w_x\),把“重心”移過去,會使該兒子子樹內包括該兒子所有點到“重心”的距離\(-w_x\),其他點\(+w_x\),所以當\((該兒子子樹中包括該兒子的點權和)\geq(其他點的點權和)\)即\(s_x\times 2\geq s_1\)時,該兒子比根更優。可以發現這種兒子至多有一個。重復該過程,直到“重心”無處可移。
由上述過程可以得出,要找的點是深度最深的滿足\(S_x\geq S_1-S_x\)即\(S_x\times2\leq S_1\)(1)的點。
通過移動“重心”的過程可以知道,滿足(1)的點要么就是點1,要么就是一條從根出發的直鏈。從根開始dfs時,無論先走哪個兒子,都有每個點在dfs序中肯定在它的祖先的后面。
所有這相當於求dfs序中最靠后的滿足(1)的點。
可以樹剖維護dfs序的區間最大\(S_x\),再二分。
代碼
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
#define LL long long
#define maxn 100005
#define maxm (maxn<<1)
#define view(u,k) for(int k=fir[u];k!=-1;k=nxt[k])
#define ls (u<<1)
#define rs (u<<1|1)
#define mi (l+r>>1)
#define lt (x&-x)
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*f;
}
void write(LL x)
{
if(x==0){putchar('0'),putchar('\n');return;}
int f=0;char ch[20];
if(x<0)putchar('-'),x=-x;
while(x)ch[++f]=x%10+'0',x/=10;
while(f)putchar(ch[f--]);
putchar('\n');
return;
}
LL up[maxn<<2],dn[maxn<<2],w[maxm],sumall,sz[maxn<<2],mksz[maxn<<2],dis[maxn],sumlen,tr[maxn<<2],ad[maxn<<2],mk[maxn<<2];
int n,q,fir[maxn],nxt[maxm],v[maxm],cnt,tofa[maxn];
int dfn[maxn],to[maxn],tim,dep[maxn],siz[maxn],son[maxn],fa[maxn],top[maxn];
void ade(int u1,int v1,int w1){v[cnt]=v1,w[cnt]=w1,nxt[cnt]=fir[u1],fir[u1]=cnt++;}
void getson(int u)
{
siz[u]=1;
view(u,k)if(v[k]!=fa[u])
{
fa[v[k]]=u,dep[v[k]]=dep[u]+1,dis[v[k]]=dis[u]+w[k],tofa[v[k]]=w[k],getson(v[k]),siz[u]+=siz[v[k]];
if(siz[son[u]]<siz[v[k]])son[u]=v[k];
}
}
void gettop(int u,int anc)
{
dfn[u]=++tim,to[tim]=u,top[u]=anc;
if(son[u])gettop(son[u],anc);
view(u,k)if(v[k]!=fa[u]&&v[k]!=son[u])gettop(v[k],v[k]);
}
void mark(int u,int k){sz[u]+=k,mksz[u]+=k;return;}
void mark2(int u,int k){tr[u]+=k*ad[u],mk[u]+=k;}
void pd2(int u){if(mk[u]){mark2(ls,mk[u]),mark2(rs,mk[u]),mk[u]=0;}}
void pd(int u){if(mksz[u])mark(ls,mksz[u]),mark(rs,mksz[u]),mksz[u]=0;}
void build(int u,int l,int r)
{
if(l==r){ad[u]=tofa[to[l]];return;}
build(ls,l,mi),build(rs,mi+1,r),ad[u]=ad[ls]+ad[rs];
}
void addsz(int u,int l,int r,int x,int y,int k)
{
if(x<=l&&r<=y){mark(u,k),mark2(u,k);return;}
pd(u),pd2(u);
if(x<=mi)addsz(ls,l,mi,x,y,k);
if(y>mi)addsz(rs,mi+1,r,x,y,k);
sz[u]=max(sz[ls],sz[rs]),tr[u]=tr[ls]+tr[rs];return;
}
LL que(int u,int l,int r,int x,int y)
{
if(x<=l&&r<=y)return tr[u];
pd2(u);LL res=0;
if(x<=mi)res=que(ls,l,mi,x,y);
if(y>mi)res+=que(rs,mi+1,r,x,y);
return res;
}
int ask(int u,int l,int r)
{
if(l==r)return l;
pd(u);
if((sz[rs]<<1)>=sumall)return ask(rs,mi+1,r);
return ask(ls,l,mi);
}
void addrd(int u,int k)
{
while(top[u]!=1)addsz(1,1,n,dfn[top[u]],dfn[u],k),u=fa[top[u]];
addsz(1,1,n,1,dfn[u],k);
}
LL askrd(int u)
{
LL res=0;
while(top[u]!=1)res+=que(1,1,n,dfn[top[u]],dfn[u]),u=fa[top[u]];
res+=que(1,1,n,1,dfn[u]);return res;
}
int main()
{
memset(fir,-1,sizeof(fir));
n=read(),q=read();
rep(i,1,n-1){int x=read(),y=read(),z=read();ade(x,y,z),ade(y,x,z);}
getson(1),gettop(1,1),build(1,1,n);
while(q--)
{
int x=read(),y=read();addrd(x,y),sumall+=y,sumlen+=dis[x]*y;
int wt=to[ask(1,1,n)];LL tmp=askrd(wt);
write(sumlen+sumall*dis[wt]-tmp*2);
}
return 0;
}
