bzoj3924 [Zjoi2015]幻想乡战略游戏(树链剖分)

Description

 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点u上,并且空地v上有dv个单位的军队,那么幽香每天就要花费dv×dist(u,v)的金钱来补给这些军队。由于幽香需要补给所有的军队,因此幽香总共就要花费为Sigma(Dv*dist(u,v),其中1<=V<=N)的代价。其中dist(u,v)表示u个v在树上的距离(唯一路径的权和)。 因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

PDF版试题:JudgeOnline/upload/201708/zjoi2015d1.pdf

Input

第一行两个数n和Q分别表示树的点数和幽香操作的个数,其中点从1到n标号。 
接下来n-1行,每行三个正整数a,b,c,表示a和b之间有一条边权为c的边。 
接下来Q行,每行两个数u,e,表示幽香在点u上放了e单位个军队
(如果e<0,就相当于是幽香在u上减少了|e|单位个军队,说白了就是du←du+e)。
数据保证任何时刻每个点上的军队数量都是非负的。 
1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5
对于所有数据,这个树上所有点的度数都不超过20
N,Q>=1

Output

 对于幽香的每个操作,输出操作完成以后,每天的最小花费,也即如果幽香选择最优的补给点进行补给时的花费。 

Sample Input

10 5

1 2 1

2 3 1

2 4 1

1 5 1

2 6 1

2 7 1

5 8 1

7 9 1

1 10 1

3 1

2 1

8 1

3 1

4 1

Sample Output

0

1

4

5

6

[ Submit][ Status][ Discuss]



分析:
题意:维护带修改的带权重心到其余点的带权距离和

怎么求出带权重心呢?
考虑一个点 x x ,我们维护一个值f[x]表示 x x 所在子树所有结点的权值和
那么如果存在一个点y y y x的一个子结点,使得 f[y]2> f [ y ] ∗ 2 > 树中所有点的权值和
那么 y y 一定比x更优,反之 x x y更优

那么一种比较朴素的想法就是,从根结点开始一路向下找
然而这样每次会遍历一些没有用的结点
因此我们考虑树链剖分,这样就可以得到一个dfs序
然后用线段树维护某一个区间 f f 的最大值
因为链剖得到的dfs序中深度较深的节点在较靠后的位置
查询时就可以用二分的思想:每次将对应的区间分成[l,mid],[mid+1,r]
只要 [mid+1,r] [ m i d + 1 , r ] 区间的最大值满足条件就跳转到 [mid+1,r] [ m i d + 1 , r ] ,直到找到一个点为止

第二问:
dis d i s :点 i i 到根的距离
loc:补给站选取的位置
sum[i] s u m [ i ] :表示 i i 子树中军队的个数

ans=sum[i]dis[i]+sum[i]dis[loc]2sum[i]dis[lca(loc,i)]

答案分成三部分,第二部分可以直接计算求解
第一部分用线段树维护区间和,每次修改的时候只需要对于需要修改的点进行单点修改

关键是第三部分如何求解
每个点只会有一个重儿子,而每次查询的时候走的都是重链
考虑 lca(loc,i) l c a ( l o c , i ) 的取值,只可能是 loc l o c 到根路径上的点
如果我们能计算出每个点的贡献,那么问题就迎刃而解了

我们对于每个点,维护该点子树中的点(除去重儿子子树中的点) sum[i]dis s u m [ i ] ∗ d i s 的值
如果 loc l o c 在重儿子的子树中,该点记录的这个值就是以该点为lca的贡献值
因为我们在向上走的过程中需要从一个重链跳到另一个重链,而走的这条边是轻边,这个特殊点需要单独计算

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long 

using namespace std;

const int N=100005;
int n,m,st[N],tot;
int size[N<<2],son[N],deep[N],pre[N],top[N],num[N],shu[N],clo=0,Sum;
ll dis[N];
struct node{
    int y,nxt,v;
};
node way[N<<1];
struct Tree{
    ll tr,val,sum,dis,ad;
};
Tree t[N<<2];

void add(int u,int w,int z)
{
    tot++;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].y=u;way[tot].v=z;way[tot].nxt=st[w];st[w]=tot;
}

void dfs_1(int now,int fa,int dep)
{
    deep[now]=dep;size[now]=1;pre[now]=fa;
    int maxx=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa)
        {
            dis[way[i].y]=dis[now]+(ll)way[i].v;
            dfs_1(way[i].y,now,dep+1);
            size[now]+=size[way[i].y];
            if (size[way[i].y]>maxx)
                maxx=size[way[i].y],son[now]=way[i].y;
        }
}

void dfs_2(int now,int fa)
{
    if (son[fa]!=now) top[now]=now;
    else top[now]=top[fa];
    num[now]=++clo; shu[clo]=now;
    if (son[now])
    {
        dfs_2(son[now],now);
        for (int i=st[now];i;i=way[i].y)
            if (way[i].y!=fa&&way[i].y!=son[now])
                dfs_2(way[i].y,now);
    }
}

void update(int now)
{
    int lc=now<<1;
    int rc=now<<1|1;
    t[now].sum=max(t[lc].sum,t[rc].sum);
    t[now].val=t[lc].val+t[rc].val;
    t[now].tr=t[lc].val+t[rc].val;
    t[now].dis=t[lc].dis+t[rc].dis;
}

void build(int bh,int l,int r)
{
    if (l==r)
    {
        t[bh].val=0; t[bh].dis=dis[shu[l]];
        t[bh].sum=0; t[bh].tr=0;
        //val表示lca为该点,轻边相连的子树与重儿子之间对答案的贡献
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid+1,r);
    update(bh);
}

void push(int now)
{
    if (t[now].ad)
    {
        t[now<<1].ad+=t[now].ad; t[now<<1|1].ad+=t[now].ad;
        t[now<<1].sum+=t[now].ad; t[now<<1|1].sum+=t[now].ad;
        t[now].ad=0;
    }
}

void addsize(int now,int l,int r,int L,int R,int v)
{
    if (l>=L&&r<=R)
    {
        t[now].ad+=(ll)v;
        t[now].sum+=(ll)v;
        return;
    }
    push(now);
    int mid=(l+r)>>1;
    if (L<=mid) addsize(now<<1,l,mid,L,R,v);
    if (R>mid) addsize(now<<1|1,mid+1,r,L,R,v);
    update(now);
}

void change(int bh,int l,int r,int x,ll v,int ff)
{
    if (l==r){
        if (!ff) t[bh].val+=v;
        else t[bh].tr=v;
        return;
    }
    push(bh);
    int mid=(l+r)>>1;
    if (x<=mid) change(bh<<1,l,mid,x,v,ff);
    else change(bh<<1|1,mid+1,r,x,v,ff);
    update(bh);
}

void solve(int x,int y,int z)
{
    change(1,1,n,num[x],(ll)size[y]*dis[y],1);
    change(1,1,n,num[y],(ll)z*dis[y],0);
    int f1=top[x],f2=top[y];
    while (f1!=f2)
    {
        addsize(1,1,n,num[f2],num[y],z);
        change(1,1,n,num[pre[f2]],(ll)z*dis[pre[f2]],0);
        y=pre[f2]; f2=top[y];
    }
    addsize(1,1,n,num[x],num[y],z);
}

int find(int now,int l,int r)
{
    if (l==r) return l;
    push(now);
    int mid=(l+r)>>1;
    if (t[now<<1|1].sum*2>=Sum) return find(now<<1|1,mid+1,r);
    else return find(now<<1,l,mid);
}

ll ask(int now,int l,int r,int L,int R)
{
    if (l>=L&&r<=R) return t[now].val;
    int mid=(l+r)>>1;
    push(now);
    ll ans=0;
    if (L<=mid) ans+=ask(now<<1,l,mid,L,R);
    if (R>mid) ans+=ask(now<<1|1,mid+1,r,L,R);
    return ans;
}

Tree findpo(int now,int l,int r,int x)
{
    if (l==r) return t[now];
    push(now);
    int mid=(l+r)>>1;
    if (x<=mid) return findpo(now<<1,l,mid,x);
    else return findpo(now<<1|1,mid+1,r,x);
}

ll solve_(int x,int y)
{
    ll ans=0;
    Tree a=findpo(1,1,n,num[y]);
    ans-=a.val;
    int f1=top[x],f2=top[y];
    while (f1!=f2)
    {
        ans+=ask(1,1,n,num[f2],num[y]);
        y=pre[f2];
        Tree a=findpo(1,1,n,num[y]);
        Tree b=findpo(1,1,n,num[f2]);
        ans=ans-a.val+dis[y]*(a.sum-b.sum);
        f2=top[y];
    }
    ans+=ask(1,1,n,num[x],num[y]);
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++)
    {
        int u,w,z;
        scanf("%d%d%d",&u,&w,&z);
        add(u,w,z);
    }
    dfs_1(1,0,1);
    dfs_2(1,0);
    memset(size,0,sizeof(size)); Sum=0;
    build(1,1,n);
    for (int i=1;i<=m;i++)
    {
        int x,z;
        scanf("%d%d",&x,&z);
        Sum+=z; size[x]+=z; 
        solve(1,x,z);
        int p=find(1,1,n); p=shu[p];
        ll ans=0;
        ans+=(ll)(dis[p]*Sum); ans+=t[1].tr;
        Tree a=findpo(1,1,n,num[p]);
        ans-=(ll)2*solve_(1,p); ans-=(ll)2*dis[p]*a.sum;
        printf("%lld\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值