洛谷3345:幻想乡战略游戏(动态树分治)

现在终于下午停课了,就是说我只要熬过早上就可以尽情怠惰专心学习了。
题面

由于我还没入东方,所以就由我来粗鄙地口胡题意吧:
一棵n个点的树,边有长度,点有点权d。每次更改一个点的点权,然后让你找一个点x,最小化

i=1ndis(i,x)d[i]
输出这个最小值。

经过一轮膜大佬的题解,我发现了原来有一个叫带权重心的东西。本题的点x就是带权重心。
定义为删掉该点后,得到的连通块权值和最大的最小的点。
性质为所有点到它的带权距离和最小。

设siz[x]为子树x的权值和。
对于原树的点x和x的儿子son。若siz[son]*2>total。则带权重心在子树son中。若没有这样的儿子,则x即为带权重心。由此思想,我们可以由根开始一路往下找,直到没有这样儿子的点为止。

这样貌似就可以ac。这个算法随着树的深度增大就变得灰常慢。所以我们需要减小树的深度,据此就想到点分树。

我们先构出原树的不带权点分树,siz[x]以为x为重心的连通块的权值和。对于点x和x在点分树上的儿子son,若siz[son]*2>total。则带权重心在连通块son中。
x指向连通块son的边指向u,把点分树上u到son路径上的点的siz全部加上(siz[x]-siz[son])。再去连通块son中找,找到后再把加上的减回去。

考虑如何统计答案。我们还要再维护几个信息,设p[x]为x在点分树上的父亲,记h[x]为仅考虑x的连通块,选p[x]时的答案。g[x]为x在点分树上所有儿子的h之和。

设带权重心为x,由x一直往点分树的父亲上走,设当前走到y
g[p[y]]-h[y]+dis(p[y],x)*(siz[p[y]]-siz[x]) 可以贡献答案。

据说要用一种O(1)求lca的才不超时,具体是这样的:
在树的欧拉序中,x和y第一次出现的位置之间的序列中深度最小的点就是x和y的lca。可用RMQ维护。

复杂度 Nlog2N

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>

using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))

typedef long long LL;

const int N=200200,oo=1e9;

void read(int &hy)
{
    hy=0;
    char cc=getchar();
    while(cc<'0'||cc>'9')
    cc=getchar();
    while(cc>='0'&&cc<='9')
    {
        hy=(hy<<3)+(hy<<1)+cc-48;
        cc=getchar();
    }
}

int to[N],nex[N],head[N],son[N],cnt;
int n,m,bigroot;
bool vis[N];
int w[N],tim[N],times;
int ff[N],p[N],siz[N],gg[N],sum,root;
int b[2*N],er[20],rmq[2*N][19];
LL val[N],dep[N],f[N],g[N],h[N],sumd;
LL ans;

void add(int u,int v,LL va)
{
    to[++cnt]=v;
    val[cnt]=va;
    nex[cnt]=head[u];
    head[u]=cnt;
}

void ljdfs(int x,int fa)
{
    w[++times]=x;
    tim[x]=times;
    for(int h=head[x];h;h=nex[h])
    if(to[h]!=fa)
    {
        dep[to[h]]=dep[x]+val[h];
        ljdfs(to[h],x);
        w[++times]=x;
    }
}

void pre()
{
    ljdfs(1,1);
    er[0]=1;
    for(int i=1;i<=20;i++)
    er[i]=er[i-1]*2;
    b[1]=0;
    for(int i=2;i<=times;i++)
    if(er[b[i-1]]*2<=i)
    b[i]=b[i-1]+1;
    else
    b[i]=b[i-1];

    dep[0]=oo;

    for(int i=1;i<=times;i++)
    rmq[i][0]=w[i];
    for(int j=1;j<=18;j++)
    for(int i=1;i<=times;i++)
    if(dep[rmq[i][j-1]]<dep[rmq[i+er[j-1]][j-1]])
    rmq[i][j]=rmq[i][j-1];
    else
    rmq[i][j]=rmq[i+er[j-1]][j-1];
}

LL dis(int x,int y)
{
    if(x==y)
    return 0;
    if(tim[x]>tim[y])
    swap(x,y);
    int hy=b[tim[y]-tim[x]];
    int lca;
    if(dep[rmq[tim[x]][hy]]<dep[rmq[tim[y]-er[hy]+1][hy]])
    lca=rmq[tim[x]][hy];
    else
    lca=rmq[tim[y]-er[hy]+1][hy];
    return dep[x]+dep[y]-2*dep[lca];
}

void dfs(int x,int fa)
{
    ff[x]=fa;
    gg[x]=0;
    siz[x]=1;
    for(int h=head[x];h;h=nex[h])
    if(to[h]!=fa&&!vis[to[h]])
    {
        dfs(to[h],x);
        siz[x]+=siz[to[h]];
        gg[x]=max(gg[x],siz[to[h]]);
    }
    gg[x]=max(gg[x],sum-siz[x]);
    if(gg[x]<gg[root])
    root=x;
}

void dfs2(int x)
{
    siz[ff[x]]=sum-siz[x];
    vis[x]=1;
    for(int h=head[x];h;h=nex[h])
    if(!vis[to[h]])
    {
        sum=siz[to[h]];
        root=0;
        dfs(to[h],to[h]);
        p[root]=x;
        son[h]=root;
        dfs2(root);
    }
}

void update(int x,LL ad)
{
    sumd+=ad;
    for(int now=x;now;now=p[now])
    {
        f[now]+=ad;

        if(p[now])
        {
            LL len=dis(p[now],x);
            h[now]+=len*ad;
            g[p[now]]+=len*ad;
        }
    }
}

void find(int x)
{
    for(int h=head[x];h;h=nex[h])
    if(son[h]&&f[son[h]]*2>sumd)
    {
        int hy=f[x]-f[son[h]];
        for(int now=to[h];now!=son[h];now=p[now])
        f[now]+=hy;
        f[son[h]]+=hy;

        find(son[h]);
        for(int now=to[h];now!=son[h];now=p[now])
        f[now]-=hy;
        f[son[h]]-=hy;
        return;
    }
    root=x;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int u,v,va;
        read(u);
        read(v);
        read(va);
        add(u,v,va);
        add(v,u,va);
    }

    pre();

    gg[0]=oo;
    sum=n;
    dfs(1,1);
    bigroot=root;
    dfs2(root);

    while(m--)
    {
        int u;
        LL v;
        read(u);
        scanf("%lld",&v);
        update(u,v);
        find(bigroot);
        ans=g[root];
        for(int now=root;p[now];now=p[now])
        ans+=g[p[now]]-h[now]+dis(p[now],root)*(f[p[now]]-f[now]);
        printf("%lld\n",ans);
    }
    return 0;
}

如果你们看新番《如果有…》,看到有弹幕说“嫉妒使我旋转卡壳,嫉妒使我状态压缩…”。那也许就是本蒟蒻发的。
这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值