[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;
}
posted @ 2021-09-29 12:00  Evitagen  阅读(99)  评论(0编辑  收藏  举报