您的位置:首页 > 其它

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

2017-10-18 16:49 381 查看
现在终于下午停课了,就是说我只要熬过早上就可以尽情怠惰专心学习了。

题面

由于我还没入东方,所以就由我来粗鄙地口胡题意吧:

一棵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
,nex
,head
,son
,cnt;
int n,m,bigroot;
bool vis
;
int w
,tim
,times;
int ff
,p
,siz
,gg
,sum,root;
int b[2*N],er[20],rmq[2*N][19];
LL val
,dep
,f
,g
,h
,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;
}


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

<
a5db
img src="https://img-blog.csdn.net/20171018185742211?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcTU4MjExNjg1OQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title="">
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: