[ZJOI2015] 幻想乡战略游戏
之前写过一遍点分树做法,但比较抽象,而且复杂度比较劣。今天学了一下树剖做法。
做法
有结论:只需要考虑点权,补给点设在带权重心上。
考虑反证法,令 \(x\) 表示重心,\(u\) 表示最优补给点,\(sz_i\) 表示 \(x\) 子树内点权之和,当 \(u\neq x\) 时,令 \(u\) 到 \(x\) 方向的边为 \((u,v,w)\),如果把最优补给点移动到 \(v\),则对答案的贡献为 \(w\cdot(sz_u-sz_v)<0\),与最优的假设矛盾。
有了这一结论,考虑如何快速找重心。由于重心的每个子树大小都不超过 \(sz_{rt}/2\),显然有 \(2sz_x\ge sz_{rt}\),且 \(sz_x\) 最小,即 \(dep_x\) 最大。考虑在 DFS 序上做线段树二分,找到最后一个满足 \(2sz_x\ge sz_{rt}\) 的点即可。
考虑这样为什么是对的。当存在两个点使得 \(2sz_x=sz_{rt}\) 时,任取即可;否则,满足条件的点一定构成一条到根的链,因此 DFS 序最大的点就是深度最大的点。
找到重心后,考虑维护答案。有:
\[\large
\begin{align*}
ans=&\sum_idis(x,i)d_i\\
=&\sum_i(dis_x+dis_i-2dis_{\mathrm{lca}(x,i)})d_i\\
=&dis_x\sum_id_i+\sum_idis_i\cdot d_i-2\sum_idis_{\mathrm{lca(x,i)}}\cdot d_i
\end{align*}
\]
前面两项是好维护的,考虑如何维护最后一项。
枚举 \(\mathrm{lca}(x,i)\),有:
\[\large
\begin{align*}
&\sum_{i\in\mathrm{anc}(x)}dis_i(sz_i-sz_{son_i})\\
=&\sum_{i\in\mathrm{anc}(x)}(dis_i-dis_{fa_i})sz_i
\end{align*}
\]
在每个点维护 \(dis_i-dis_{fa_i}=w_i\) 即可。
实现上,用线段树维护每个点的 \(sz_i\) 和 \(w_i\cdot sz_i\) 即可。
代码
#include <iostream>
#define int long long
const int N=1e5+5;
int n,Q,hed[N],tal[N<<1],wt[N<<1],nxt[N<<1],cnte,li[N],wa[N];
void de(int u,int v,int w) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte,wt[cnte]=w;}
#define mid (lb+rb>>1)
struct Node
{
int wsz,ddis,sum,tg;
void mt(int t) {wsz+=t,sum+=ddis*t,tg+=t;}
} tr[N<<2];
void pd(int x)
{
int &t=tr[x].tg;
tr[x<<1].mt(t),tr[x<<1|1].mt(t),t=0;
}
void pu(int x)
{
tr[x].wsz=std::max(tr[x<<1].wsz,tr[x<<1|1].wsz);
tr[x].ddis=tr[x<<1].ddis+tr[x<<1|1].ddis;
tr[x].sum=tr[x<<1].sum+tr[x<<1|1].sum;
}
void build(int x,int lb,int rb)
{
if(lb==rb) return tr[x]={0,wa[li[lb]],0,0},void();
build(x<<1,lb,mid),build(x<<1|1,mid+1,rb),pu(x);
}
void md(int x,int l,int r,int k,int lb,int rb)
{
if(l<=lb&&rb<=r) return tr[x].mt(k);
pd(x);
if(l<=mid) md(x<<1,l,r,k,lb,mid);
if(r>mid) md(x<<1|1,l,r,k,mid+1,rb);
pu(x);
}
int qr(int x,int k,int lb,int rb)
{
if(lb==rb) return li[lb];
pd(x);
if(tr[x<<1|1].wsz*2>=k) return qr(x<<1|1,k,mid+1,rb);
return qr(x<<1,k,lb,mid);
}
int qsum(int x,int l,int r,int lb,int rb)
{
if(l<=lb&&rb<=r) return tr[x].sum;
pd(x);
int ret=0;
if(l<=mid) ret=qsum(x<<1,l,r,lb,mid);
if(r>mid) ret+=qsum(x<<1|1,l,r,mid+1,rb);
return ret;
}
#undef mid
int dfn[N],dis[N],dep[N],fa[N],son[N],sz[N],top[N],idx,sum,wsum;
void dfs1(int x)
{
sz[x]=1;
for(int i=hed[x],v;i;i=nxt[i]) if(!sz[v=tal[i]])
fa[v]=x,dep[v]=dep[x]+1,dis[v]=dis[x]+wt[i],wa[v]=wt[i],
dfs1(v),sz[x]+=sz[v],sz[v]>sz[son[x]]?son[x]=v:0;
}
void dfs2(int x,int tp)
{
if(!x) return;
dfn[x]=++idx,li[idx]=x,dfs2(son[x],top[x]=tp);
for(int i=hed[x],v;i;i=nxt[i]) if(!top[v=tal[i]]) dfs2(v,v);
}
void md(int x,int k)
{
while(x)
md(1,dfn[top[x]],dfn[x],k,1,n),
x=fa[top[x]];
}
int qr(int x)
{
int ret=0;
while(x) ret+=qsum(1,dfn[top[x]],dfn[x],1,n),x=fa[top[x]];
return ret;
}
signed main()
{
std::cin.tie(0)->sync_with_stdio(0);
std::cin>>n>>Q;
for(int i=1,u,v,w;i<n;i++) std::cin>>u>>v>>w,de(u,v,w),de(v,u,w);
dfs1(1),dfs2(1,1),build(1,1,n);
for(int i=1,x,y;i<=Q;i++)
{
std::cin>>x>>y;
sum+=y,wsum+=dis[x]*y,md(x,y);
int rt=qr(1,sum,1,n);
std::cout<<dis[rt]*sum+wsum-2*qr(rt)<<'\n';
}
}

浙公网安备 33010602011771号