bzoj3924 [Zjoi2015]幻想乡战略游戏(动态点分治)

就是求带权重心,可以修改点权。
我们首先建出重心树。对于每个节点x记
s1[x]–x的子树到x的答案,
s2[x]–x的子树的点权和,
s3[x]–x的子树到fa[x]的答案。
那我们就可以通过这些信息得出以x为重心的答案(在重心树上一直往上跳,复杂度O(log n).)
修改点权时,最多影响logn个点,我们就O(logn)的更新一下这些点的信息。
怎么找带权重心呢?从重心树的根开始,枚举它的所有出边(原树中的边),如果往那个方向走得到的答案更优,就走到那个方向的重心树中的儿子上去,否则最优的就是当前点了。(可以证明最多只有一个方向比我目前的点优,如果一个方向紧邻我的点的答案比我大,则这个方向的所有点的答案都肯定比我大。)
复杂度 O(nlog2n20) O ( n l o g 2 n ∗ 20 )

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 100010
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int 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;
}
int n,m,fa[N],sz[N],f[N],sumsz,rt,h[N],num=0,root,dis[N];
int dep[N],mn[N<<1][20],dfn[N],dfnum=0,Log[N<<1];
ll s1[N],s2[N],s3[N];//s1[x]--x的子树到x的答案,s2[x]--x的子树的点权和,s3[x]--x的子树到fa[x]的答案
struct edge{
    int to,next,val;
}data[N<<1];
bool vis[N];
vector<int>son[N],Gson[N];
inline void dfs(int x,int Fa){
    mn[++dfnum][0]=x;dfn[x]=dfnum;
    for(int i=h[x];i;i=data[i].next){
        int y=data[i].to;if(y==Fa) continue;
        dep[y]=dep[x]+1;dis[y]=dis[x]+data[i].val;
        dfs(y,x);mn[++dfnum][0]=x;
    }
}
inline void inirmq(){
    Log[0]=-1;
    for(int i=1;i<=n<<1;++i) Log[i]=Log[i>>1]+1;
    for(int i=1;i<=Log[n<<1];++i)
        for(int j=1;j<=2*n-1;++j){
            if(j+(1<<i-1)>2*n-1) break;
            mn[j][i]=dep[mn[j][i-1]]<dep[mn[j+(1<<i-1)][i-1]]?mn[j][i-1]:mn[j+(1<<i-1)][i-1];
        }
}
inline void dfs1(int x,int Fa){
    sz[x]=1;
    for(int i=h[x];i;i=data[i].next){
        int y=data[i].to;if(vis[y]||y==Fa) continue;
        dfs1(y,x);sz[x]+=sz[y];
    }
}
inline void dfs2(int x,int Fa){
    f[x]=0;
    for(int i=h[x];i;i=data[i].next){
        int y=data[i].to;if(vis[y]||y==Fa) continue;
        dfs2(y,x);f[x]=max(f[x],sz[y]);
    }f[x]=max(f[x],sumsz-sz[x]);if(f[x]<f[rt]) rt=x;
}
inline void getG(int x){
    vis[x]=1;dfs1(x,0);
    for(int i=h[x];i;i=data[i].next){
        int y=data[i].to;if(vis[y]) continue;
        sumsz=sz[y];rt=0;dfs2(y,0);fa[rt]=x;
        son[x].push_back(y);Gson[x].push_back(rt);getG(rt);
    }
}
inline int lca(int x,int y){
    x=dfn[x],y=dfn[y];if(x>y) swap(x,y);
    int t=Log[y-x+1];
    return dep[mn[x][t]]<dep[mn[y-(1<<t)+1][t]]?mn[x][t]:mn[y-(1<<t)+1][t];
}
inline int caldis(int x,int y){
    return dis[x]+dis[y]-2*dis[lca(x,y)];
}
inline void change(int x,int val){
    for(int i=x;i;i=fa[i]){
        s1[i]+=(ll)caldis(i,x)*val;
        s2[i]+=val;
        if(fa[i]) s3[i]+=(ll)caldis(x,fa[i])*val;
    }
}
inline ll calc(int x){
    ll res=s1[x];
    for(int i=x;fa[i];i=fa[i]){
        res+=s1[fa[i]]-s3[i]+(s2[fa[i]]-s2[i])*caldis(x,fa[i]);
    }return res;
}
inline ll ask(int x){
    ll res=calc(x);
    for(int i=0;i<son[x].size();++i){
        int y=son[x][i];
        if(calc(y)<res) return ask(Gson[x][i]);
    }return res;
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();f[0]=inf;
    for(int i=1;i<n;++i){
        int x=read(),y=read(),val=read();
        data[++num].to=y;data[num].next=h[x];h[x]=num;data[num].val=val;
        data[++num].to=x;data[num].next=h[y];h[y]=num;data[num].val=val;
    }dfs(1,0);inirmq();
    dfs1(1,0);sumsz=n;rt=0;dfs2(1,0);fa[rt]=0;root=rt;getG(rt);
    while(m--){
        int x=read(),val=read();
        change(x,val);printf("%lld\n",ask(root));
    }return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值