[ZJOI2015]幻想乡战略游戏
[ZJOI2015]幻想乡战略游戏
题意:
给定一个树,每个节点上有 \(a_i\) 个人,每条边有长度。
现在设定一个节点,每个人都要到这个节点,消耗人数 \(*\) 距离的价值。
给定 \(Q\) 个询问,每个询问可以增加/减少一个点上的人。
问你每次消耗价值的最小值。
分析:
这题虽然看起来像是点分治,实际上可以用树链剖分来写:
本题中,重心位置和点权/边权无关。
证明:
我们换一种方式计算答案,枚举边 \(edge[i]\) ,这条边把树分成两部分,两个端点是 \(x_i,y_i\) ,两棵子树的大小为 \(size[x],size[y]\) ,则答案为:
\[ans=\sum edge[i]*sizes[x]*sizes[y]
\]
实际上,重心的位置和不带权树的重心位置是一样的。因此我们找到整个图的重心,其消耗的价值一定最少。
快速找到重心:
我们用线段树维护左儿子/右儿子的子树大小,看是否满足 \(2sizes[x]>sizes[1]\) ,且 \(x\) 最深。
我们在线段树上二分判断左儿子/右儿子子树大小,选择符合条件的继续向下搜索,最后就是重心。
求答案:
我们设 \(e(y)=sizes[x]*sizes[y]\)
则有:
\[ans=\sum dis(x,y) * e(y)
\]
我们依次转化:
\[\sum dis(x,root) * e(y)+ \sum dis(y,root)*e(y)- 2\sum dis(lca,root) *e(y)
\]
我们设点权为 \(w[x]=edge(x,fa(x))\).
每次修改一个点的时候,就把它到根的大小 \(sizes\) 全部加上修改的值,查询重心到根,每个点 \(sizes[x]*w[x]\), 这是因为两个点到 \(lca\) 到根的路径是两个点的共同路径。
代码:
// [ZJOI2015]幻想乡战略游戏
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int n,Q;
int nxt[N],ver[N],tot,edge[N],head[N];
int dfn[N],sizes[N],dis[N],son[N],id[N],cnt,dep[N],fa[N],rk[N],top[N],wt[N];
void add(int x,int y,int z){
ver[++tot]=y; edge[tot]=z; nxt[tot]=head[x]; head[x]=tot;
}
void dfs1(int x,int father){
sizes[x]=1;
dep[x]=dep[father]+1;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i],z=edge[i];
if(y==father) continue;
fa[y]=x;
dis[y]=dis[x]+z;
dfs1(y,x);
sizes[x]+=sizes[y];
if(sizes[son[x]]<sizes[y]) son[x]=y;
}
}
void dfs2(int x,int topfather){
dfn[x]=++cnt; rk[cnt]=x;
top[x]=topfather;
wt[cnt]=dis[x]-dis[fa[x]];//该节点对应的距离父亲的长度
if(!son[x]) return ;
dfs2(son[x],topfather);
for(int i=head[x];i;i=nxt[i]){
int y=ver[i]; if(y==fa[x]||y==son[x]) continue;
dfs2(y,y);
}
}
struct node{
int l,r,sz,lazy;
int dis,sum;
}t[N<<1];
// 线段树叶子维护子树大小,其他节点维护线段树上子节点大小最大值
void build(int x,int l,int r){
t[x].l=l; t[x].r=r;
if(l==r){
t[x].dis=wt[l]; //每次修改一个点时,就把它到根的大小size全部加上修改的值
return;
}
int mid=l+r>>1;
build(x<<1,l,mid); build(x<<1|1,mid+1,r);
t[x].dis=t[x<<1].dis+t[x<<1|1].dis;
}
void pushup(int x){
t[x].sz=max(t[x<<1].sz,t[x<<1|1].sz);
t[x].sum=t[x<<1].sum+t[x<<1|1].sum;
}
void pushdown(int x){
if(t[x].lazy){
t[x<<1].sz+=t[x].lazy; t[x<<1|1].sz+=t[x].lazy;
t[x<<1].lazy+=t[x].lazy; t[x<<1|1].lazy+=t[x].lazy;
t[x<<1].sum+=t[x].lazy*t[x<<1].dis;
t[x<<1|1].sum+=t[x].lazy*t[x<<1|1].dis;
t[x].lazy=0;
}
}
void add(int x,int l,int r,int c){
if(t[x].l>=l&&t[x].r<=r){
t[x].sz+=c; t[x].lazy+=c;
t[x].sum+=c*t[x].dis;
return;
}
pushdown(x);
int mid=t[x].l+t[x].r>>1;
if(l<=mid) add(x<<1,l,r,c);
if(r>mid) add(x<<1|1,l,r,c);
pushup(x);
}
int query(int x,int l,int r){
if(t[x].l>=l&&t[x].r<=r) return t[x].sum;
pushdown(x);
int mid=t[x].l+t[x].r>>1;
int res=0;
if(l<=mid) res+=query(x<<1,l,r);
if(r>mid) res+=query(x<<1|1,l,r);
return res;
}
int weight(){//寻找重心,
int x=1,L=1,R=n;
while(L<R){
pushdown(x);
int mid=L+R>>1;
if(t[x<<1|1].sz*2>t[1].sz) L=mid+1,x=(x<<1|1);
else R=mid,x=(x<<1);
}
return rk[L];
}
void range_add(int x,int y){
while(top[x]!=1){
add(1,dfn[top[x]],dfn[x],y);
x=fa[top[x]];
}
add(1,1,dfn[x],y);
}
int range_query(int x){
int res=0;
while(top[x]!=1){
res+=query(1,dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
res+=query(1,1,dfn[x]);
return res;
}
int sum_dis_e,sum_e;
int getans(int x){
return sum_dis_e+dis[x]*sum_e-2*range_query(x);
}
signed main(){
scanf("%lld%lld",&n,&Q);
for(int i=1,x,y,z;i<n;i++){
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
dfs1(1,0); dfs2(1,1);
build(1,1,n);
while(Q--){
int x,y; scanf("%lld%lld",&x,&y);
sum_e+=y;
sum_dis_e+=dis[x]*y;
range_add(x,y);
printf("%lld\n",getans(weight()));
}
system("pause");
return 0;
}
不关注的有难了😠😠😠https://b23.tv/hoXKV9