[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在树上的距离(唯一路径的权和)。 因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

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


样例有误,输入边的地方有两行边权和点的编号中间没加空格……
话说一遍过还真是舒服啊
难道咱有写工业题的潜质?
(今早NOIP模拟题T1誓死不用STL写出280+行,STL党仅需120+行(虽然还是A了))


思路:
如果可以动态维护每个点的答案值,那么就可以很稳地做出此题。

可以发现,如果咱记录点u的子树点权和sumd[u],那么如果u到当前要计算值的点需要经过其父亲边向父亲走,那么这条父亲边对答案的贡献便是其权值*sumd[u]。
那么考虑维护sumd[u]来求答案。
考虑到出题人必须要足够坑的基本素质,每次如果暴力修改,复杂度会是是O(n)的,单次询问由于需要计算经过的点,复杂度也是O(n)的。
那这就会很尴尬地TLE了……

考虑使用动态点分治优化树的结构——建出树高仅为log的点分树。
所谓点分树,就是在点分治时把各个分治中心连在一起得到的一棵树~

那么可以发现之前的式子无法直接使用边权,而且还需要减去与父亲节点之间相冲突的子树部分。
那就维护一个dis,代表以u为根的子树中,所有节点到u的父亲所需的价值和,再维护一个fadis,代表以u为根的子树中,所有节点到u所需的价值和。
(名字很奇怪对吧只是咱写代码时写反了而已)
这两个值可以在修改操作时维护一下。
那么这就可以用加减法排除掉重复子树的影响了~~

修改直接暴力从被修改点开始往上一个个暴力跳父亲,反正点分树深度log~
至于查询,可以发现(显然是错的,显然可以随手卡掉)新的答案位置在原树上不会距离上一个答案位置太远。
那就在原树上暴力跳可好??反正单点价值查询只是log~~
(10s时限只用了4s证明效果还是不错的)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;

const ll N=1e5+9;
const ll K=22;

inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x*f;
}

inline void chkmax(ll &a,ll b){if(a<b)a=b;}
inline ll minn(ll a,ll b){if(a<b)return a;return b;}

ll n,q,lans=1;
ll d[N];

namespace tree
{
    ll to[N<<1],nxt[N<<1],w[N<<1],beg[N],tot=1;
    ll id[N],dfn,st[N<<1][K],dep[N],logs[N<<1];
    ll fa[N],sumd[N],dis[N],fadis[N];
    bool ban[N<<1];

    inline void add(int u,int v,int c)
    {
        to[++tot]=v;
        nxt[tot]=beg[u];
        w[tot]=c;
        beg[u]=tot;
    }

    inline void dfs(ll u,ll fat=0)
    {
        st[++dfn][0]=dep[u];
        id[u]=dfn;
        for(ll i=beg[u],v;i;i=nxt[i])
            if((v=to[i])!=fat)
            {
                dep[v]=dep[u]+w[i];
                dfs(v,u);
                st[++dfn][0]=dep[u];
            }
    }

    inline void init()
    {
        for(ll i=2;i<=dfn;i++)
            logs[i]=logs[i>>1]+1;
        for(ll i=1;i<=logs[dfn];i++)
            for(ll j=1;j+(1<<i)-1<=dfn;j++)
                st[j][i]=minn(st[j][i-1],st[j+(1<<i-1)][i-1]);
    }

    inline ll dist(ll u,ll v)
    {
        ll ret=dep[u]+dep[v];
        u=id[u],v=id[v];
        if(u>v)swap(u,v);
        ll dis=logs[v-u+1];
        return ret-2*minn(st[u][dis],st[v-(1<<dis)+1][dis]);
    }

    inline ll get_siz(ll u,ll fa=0)
    {
        ll siz=1;
        for(ll i=beg[u];i;i=nxt[i])
            if(!ban[i] && to[i]!=fa)
                siz+=get_siz(to[i],u);
        return siz;
    }

    inline ll get_center(ll u,ll fa,ll totsiz,ll &root)
    {
        ll mxsiz=0,siz=1;
        for(ll i=beg[u],v,tmp;i;i=nxt[i])
            if(!ban[i] && (v=to[i])!=fa)
            {
                siz+=(tmp=get_center(v,u,totsiz,root));
                chkmax(mxsiz,tmp);
            }
        chkmax(mxsiz,totsiz-siz);
        if((mxsiz<<1)<=totsiz)
            root=u;
        return siz;
    }

    inline ll solve(ll u)
    {
        ll totsiz=get_siz(u),root;
        get_center(u,0,totsiz,root);
        sumd[root]=d[root];
        for(ll i=beg[root],son;i;i=nxt[i])
            if(!ban[i])
            {
                ban[i]=ban[i^1]=1;
                son=solve(to[i]);
                fa[son]=root;
                sumd[root]+=sumd[son];
            }
        return root;
    }

    inline void update(ll u,ll val)
    {
        sumd[u]+=val;
        for(ll i=u;fa[i];i=fa[i])
        {
            ll diss=dist(fa[i],u);
            sumd[fa[i]]+=val;
            dis[i]+=val*diss;
            fadis[fa[i]]+=val*diss;
        }
    }

    inline ll calc(ll u)
    {
        ll ret=fadis[u];
        for(ll i=u;fa[i];i=fa[i])
        {
            ret+=(fadis[fa[i]]-dis[i]);
            ret+=dist(fa[i],u)*(sumd[fa[i]]-sumd[i]);
        }
        return ret;
    }

    inline ll query(ll u)//no fa anymore
    {
        ll ret=calc(u);
        for(ll i=beg[u],v;i;i=nxt[i])
            if(calc(v=to[i])<ret)
                return query(v);
        lans=u;
        return ret;
    }
}

using namespace tree;

int main()
{
    if(fopen("tree.in","r"))
    {
        freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
    }

    n=read();
    q=read();

    for(ll i=1,u,v,c;i<n;i++)
    {
        u=read();
        v=read();
        c=read();
        add(u,v,c);
        add(v,u,c);
    }

    dfs(1);
    init();
    solve(1);

    for(ll i=1,u,e;i<=q;i++)
    {
        u=read();
        e=read();
        update(u,e);
        printf("%lld\n",query(lans));
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值