【BZOJ3924】【Zjoi2015】幻想乡战略游戏(树链剖分+点分治)

4 篇文章 0 订阅
1 篇文章 0 订阅

Description

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

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)。数据保证任何时刻每个点上的军队数量都是非负的。

Output

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

Sample Input

10 5

1 2 1

2 3 1

2 4 1

1 5 1

2 61

2 7 1

5 8 1

7 91

1 10 1

3 1

2 1

8 1

3 1

4 1

Sample Output

0

1

4

5

6

HINT

对于所有数据,1<=c<=1000, 0<=|e|<=1000, n<=105, Q<=105

题解:
题目等价于支持修改点权和寻找整个树的带权重心。
做点分治,如果当前节点不是重心,那么重心一定在他最大的子树里,就向下转成了子问题,最多logN层,暴力转移就好了。

代码如下:

#include<math.h>
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 0x7f7f7f7f
#define mod 1000000007
#define N 100005
#define ls (k<<1)
#define rs (k<<1|1)
#define mid (l+r>>1)
using namespace std;
ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
ll ans,tot;
int n,m,cnt,id,rt=1;
int fa[N],siz[N],dep[N],d[N],bl[N];
int pos[N],hd[N];
int sum[N*3];
struct edge
{
    int to,nex,v;
    edge(){}
    edge(int a,int b,int c):to(a),nex(b),v(c){}
}e[N<<1];
void insert(int u,int v,int w)
{
    e[++cnt]=edge(v,hd[u],w),hd[u]=cnt;
    e[++cnt]=edge(u,hd[v],w),hd[v]=cnt;
}
void modify(int k,int l,int r,int x,int val)
{
    if(l==r)
    {
        sum[k]+=val;
        return;
    }
    if(x<=mid) modify(ls,l,mid,x,val);
    else modify(rs,mid+1,r,x,val);
    sum[k]=sum[ls]+sum[rs];
}
int query(int k,int l,int r,int L,int R)
{
    if(l==L && R==r) return sum[k];
    int ans=0;
    if(L<=mid) ans+=query(ls,l,mid,L,min(R,mid));
    if(R>mid) ans+=query(rs,mid+1,r,max(L,mid+1),R);
    return ans;
}
void dfs1(int x)
{
    siz[x]=1;
    for(int i=hd[x];i;i=e[i].nex)
    {
        if(e[i].to==fa[x]) continue;
        fa[e[i].to]=x;
        dep[e[i].to]=dep[x]+1;
        d[e[i].to]=d[x]+e[i].v;
        dfs1(e[i].to);
        siz[x]+=siz[e[i].to];
    }
}
void dfs2(int x,int top)
{
    pos[x]=++id;bl[x]=top;
    int son=0;
    for(int i=hd[x];i;i=e[i].nex)
    if(e[i].to!=fa[x] && siz[e[i].to]>siz[son]) son=e[i].to;
    if(son) dfs2(son,top);
    for(int i=hd[x];i;i=e[i].nex)
    if(e[i].to!=fa[x] && e[i].to!=son) dfs2(e[i].to,e[i].to);
}
int lca(int x,int y)
{
    while(bl[x]!=bl[y])
    {
        if(dep[bl[x]]>dep[bl[y]]) x=fa[bl[x]];
        else y=fa[bl[y]];
    }
    return dep[x]<dep[y] ? x : y;
}
int dis(int x,int y){return d[x]+d[y]-2*d[lca(x,y)];}
ll cal(int x,int v)
{
    int t1,t2;
    if(x==fa[rt])
    {
        t1=query(1,1,n,pos[rt],pos[rt]+siz[rt]-1);
        t2=tot-t1;
    }
    else 
    {
        t2=query(1,1,n,pos[x],pos[x]+siz[x]-1);
        t1=tot-t2;
    }
    return ans+(ll)(t1-t2)*v;
}
void move(int x)
{
    ll mn=(ll)1e60,t;
    int id=0;
    for(int i=hd[x];i;i=e[i].nex)
    {
        t=cal(e[i].to,e[i].v);
        if(t<mn)
        {
            mn=t;
            id=e[i].to;
        }
    }
    if(mn<ans)
    {
        rt=id;
        ans=mn;
        move(rt);
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<n;i++)
    {
        int u=read(),v=read(),w=read();
        insert(u,v,w);
    }
    dfs1(1);
    dfs2(1,1);
    for(int i=1;i<=m;i++)
    {
        int u=read(),e=read();
        ans+=(ll)e*dis(u,rt);
        tot+=e;
        modify(1,1,n,pos[u],e);
        move(rt);
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值