题意
给出一棵N(<=1e5)个点的树。
特殊性质:每个点度数不超过20。
M(<=1e5)次操作,支持更改一个点的点权,每次操作后输出∑(每个点的点权*该点到带权重心距离)。
题解
关于找带权重心:
规定sum[i]表示点i子树的点权和。
如果当前在x,重心在son[x]的子树里,当且仅当sum[x]<2*sum[son[x]]
然后我们直接走过去就可以了。
多个重心的话 这样说好像并不严谨 算了感性理解吧
我们点分一发。
查询的时候从分治根开始走,枚一下所有出边。
如果发现当前重心在该边指向的分治子树里,就走到分治子树根的位置,重复上述过程。
这个复杂度是log*20的。
答案怎么统计呢?
从x向其相连的y所在子树走的时候,把x子树中除y所在子树外的点权压到y上。
这一步和直接修改点权的更新一样 都是log的。
所以就是O(nlog^2n+nlogn*20)
代码
#include<bits/stdc++.h>
#define N 100005
#define L 17
using namespace std;
typedef long long ll;
int n,m,w,rt,dep[N][L],f[N],siz[N],dp[N],ch[N<<1],
to[N<<1],hd[N<<1],lk[N],len[N<<1],stu[L],stv[L],stw[L],tot,cnt;
ll mul[N],sum[N],de[N],ans,tmp;
bool vis[N];
void ini(int x,int lst)
{
siz[x]=1;
for(int k,i=lk[x];i;i=hd[i])
if((k=to[i])^lst)
ini(k,x),siz[x]+=siz[k];
}
void dfs(int x,int y,int l)
{
siz[x]=1;
for(int k,i=lk[x];i;i=hd[i])
if(((k=to[i])^y)&&!vis[k])
dep[k][l]=dep[x][l]+len[i],
dfs(k,x,l),siz[x]+=siz[k];
}
int getr(int x,int y,int nn)
{
for(int k,i=lk[x];i;i=hd[i])
if(((k=to[i])^y)&&siz[k]>nn&&!vis[k])
return getr(k,x,nn);
return x;
}
int build(int x,int fa)
{
vis[x=getr(x,x,siz[x]>>1)]=1;
if(fa)dp[x]=dp[f[x]=fa]+1;
dfs(x,x,dp[x]);
for(int i=lk[x];i;i=hd[i])
if(!vis[to[i]])
ch[i]=build(to[i],x);
return x;
}
inline void upd(int u,int v,int y)
{
for(int i=u;;i=f[i])
{
sum[i]+=v,mul[i]+=(ll)v*dep[u][dp[i]];
if(f[i]==y)break;
de[i]+=(ll)v*dep[u][dp[i]-1];
}
}
inline void add(int u,int v)
{to[++cnt]=v,hd[cnt]=lk[u],len[cnt]=w,lk[u]=cnt;}
int u,v;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
scanf("%d%d%d",&u,&v,&w),
add(u,v),add(v,u);
ini(1,0),rt=build(1,0);
while(m--)
{
scanf("%d%d",&u,&v);
upd(u,v,0);
tot=tmp=0;
for(int i=rt;;)
{
tmp+=mul[u=i];
for(int k,j=lk[i];j;j=hd[j])
if((k=ch[j])&&sum[i]<(sum[k]<<1))
{
tmp+=(sum[i]-sum[k])*len[j]-de[k];
upd(stu[tot]=to[j],stv[tot]=sum[i]-sum[k],stw[tot]=i),tot++;
i=k;
}
if(u==i)break;
}
printf("%lld\n",tmp);
while(tot--)
upd(stu[tot],-stv[tot],stw[tot]);
}
}