您的位置:首页 > 其它

bzoj 3924: [Zjoi2015]幻想乡战略游戏 动态树分治

2017-04-22 17:06 281 查看

题意

给出一棵树,边有边权。每次操作要求修改一个点的点权d,然后求一个点p使得∑dis(p,i)∗d[i]最小。

n,q<=100000

分析

实际上也就是动态维护整棵树的带权重心。

考虑先建出分治树,每个点维护三个值sd[x]表示该点子树内的点权和,sv[x]表示该点子树内所有点到该点的带权距离,tofa[x]表示该点子树内所有点到其父亲的带权距离。这里的子树和父亲都是指分治树上的。

修改的话就暴力沿着父亲往上跳,修改路径上的值即可。

查询的话,网上有很多题解说的都是从分治树的根节点开始一直往子树走,每次都要换根然后再复原。蒟蒻表示看的瑟瑟发抖一脸懵逼。

恩实际上我们可以首先找到带权重心,然后把带权重心到分治树的根的路径跑一遍就可以得到答案了。至于具体怎么求就自己yy好啦。

那我们怎么求带权重心呢?

首先我们把原树看做有根树,然后用dfs序+树状数组来维护每个点在原树中的子树内点权和。

然后我们从树分治的根节点开始,每次枚举该节点在原树中的所有出边,如果沿着这条边走更优的话,就从该节点走到该边在分治树中所属的子树内。

这样的复杂度严格来讲是O(nlog2)的,但实际跑的飞快,估计是树状数组常数小的缘故。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

typedef long long LL;

const int N=100005;

int n,q,cnt,last
,root,a1,a[N*2],dep
,pos
,sd
,ls
,lg[N*2],rmq[N*2][25],size
,mx
,tot,fa
,sum,c
,dfn
,tim,mndfn
,mxdfn
;
LL sv
,tofa
;
struct edge{int to,len,next,to1;}e[N*3];
bool vis
;

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;
}

void addedge(int u,int v,int len)
{
e[++cnt].to=v;e[cnt].len=len;e[cnt].next=last[u];last[u]=cnt;
e[++cnt].to=u;e[cnt].len=len;e[cnt].next=last[v];last[v]=cnt;
}

void addedge1(int u,int v,int len)
{
e[++cnt].to=v;e[cnt].len=len;e[cnt].next=ls[u];ls[u]=cnt;
}

void dfs(int x,int fa)
{
pos[x]=++a1;a[a1]=dep[x];dfn[++tim]=x;mndfn[x]=mxdfn[x]=tim;
for (int i=last[x];i;i=e[i].next)
{
if (e[i].to==fa) continue;
dep[e[i].to]=dep[x]+e[i].len;
dfs(e[i].to,x);
a[++a1]=dep[x];
}
mxdfn[x]=tim;
}

void ins(int x,int y)
{
while (x<=n) c[x]+=y,x+=x&(-x);
}

int get_w(int x)
{
int l=mndfn[x]-1,r=mxdfn[x];
int ans=0;
while (r) ans+=c[r],r-=r&(-r);
while (l) ans-=c[l],l-=l&(-l);
return ans;
}

int get_len(int x,int y)
{
int l=pos[x],r=pos[y];
if (l>r) swap(l,r);
int w=lg[r-l+1];
return dep[x]+dep[y]-2*min(rmq[l][w],rmq[r-(1<<w)+1][w]);
}

void get_root(int x,int fa)
{
size[x]=1;mx[x]=0;
for (int i=last[x];i;i=e[i].next)
{
if (e[i].to==fa||vis[e[i].to]) continue;
get_root(e[i].to,x);
size[x]+=size[e[i].to];
mx[x]=max(mx[x],size[e[i].to]);
}
mx[x]=max(mx[x],tot-size[x]);
if (!root||mx[x]<mx[root]) root=x;
}

void build(int x)
{
vis[x]=1;
for (int i=last[x];i;i=e[i].next)
{
if (vis[e[i].to]) continue;
root=0;tot=size[e[i].to];
get_root(e[i].to,x);
e[i].to1=root;
fa[root]=x;addedge1(x,root,get_len(x,root));
build(root);
}
}

void modify(int x,int y,int z)
{
sd[x]+=z;sv[x]+=(LL)z*get_len(x,y);
if (!fa[x]) return;
tofa[x]+=(LL)z*get_len(y,fa[x]);
modify(fa[x],y,z);
}

int find(int x)
{
for (int i=last[x];i;i=e[i].next)
{
int w;
if (dep[e[i].to]>dep[x]) w=get_w(e[i].to);
else w=sum-get_w(x);
if (w*2>sum) return find(e[i].to1);
}
return x;
}

LL query(int x,int y,int son)
{
if (!x) return 0;
LL ans=0;
if (x==y) ans=sv[x];
else ans=sv[x]-tofa[son]+(LL)get_len(x,y)*(sd[x]-sd[son]);
return ans+query(fa[x],y,x);
}

int main()
{
n=read();q=read();
for (int i=1;i<n;i++)
{
int x=read(),y=read(),z=read();
addedge(x,y,z);
}
dfs(1,0);
for (int i=1;i<=a1;i++) lg[i]=log(i)/log(2),rmq[i][0]=a[i];
for (int j=1;j<=lg[a1];j++)
for (int i=1;i<=a1-(1<<j)+1;i++)
rmq[i][j]=min(rmq[i][j-1],rmq[i+(1<<(j-1))][j-1]);
root=0;tot=n;
get_root(1,0);
int R=root;
build(root);
while (q--)
{
int x=read(),y=read();sum+=y;
modify(x,x,y);
ins(mndfn[x],y);
int ansp=find(R);
printf("%lld\n",query(ansp,ansp,0));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: