[ZJOI2015]幻想乡战略游戏 解题报告 (动态点分治)
题意
有一棵大小为 \(n\) 的带权树, 每个点有一个权值, 权值可以修改 \(q\) 次,
找出一个补给点 \(x\) , 使得 \(\sum_{u \in V} val[u] \times dis(x,u)\) 最小, 并求出这个最小值.
一句话 : 求带权重心 (zsy说的)
附加条件 : 树中所有点的度数不超过 \(20\).
思路
一道你以为复杂度过不了, 但其实是过得了的题.
首先, 我们假定先选了一个点 \(u\) 作为补给点, 我们可以算出它的所有子节点 \(v\) 作为补给点时所需要的代价.
那么我们选取一个代价最小的点, 带权重心就一定在它的子树内;
假若点 \(u\) 的代价最小, 那么点 \(u\) 就是带权重心.
所以我们可以得到一个大致思路 :
先从一个点开始, 每次决定要往哪个子节点走, 然后在这个子节点的子树内递归处理这个子任务.
那么为了减小递归次数, 我们可以用动态点分治, 沿着点分树一层一层往下处理, 最后经过 \(\log n\) 层找到带权重心.
而我们在每次层中的操作就是 : 枚举当前点的每一个子节点, 找出最小的那个, 然后跳向它对应的重心.
那我们现在的问题就是 : 该如何计算一个点作为补给点时所需的代价.
首先, 有了动态点分治, 我们可以维护一个点作为补给点时, 它在 点分树中的子树 中的点到它的中代价, 设为 \(v1\),
那么, 设当前点为 \(u\) ,根据 \(ft[u]\) ( \(u\) 在点分树上的父亲) 的 \(v1\), 再加上 一些其他东西 , 我们就可以求出 \(ft[u]\) 在点分树中的子树的点到点 \(u\) 的总代价, 这样一层一层往上, 那我们就可以得到整棵树到点 \(u\) 的代价.
那, 这些 "其他东西" 是什么呢?
我们可以把 \(ft[u]\) 的子树分为两部分, (注 : 从这里开始, "子树" 均指 点分树上的子树.)
- 在点 \(u\) 的子树中的部分.
- 不在点 \(u\) 的子树中的部分.
第一部分所需的代价就是 \(v1[u]\),
为了计算第二部分的代价, 我们增添几个值,
- \(v2[u]\) : 表示点 \(u\) 子树中的点到 \(ft[u]\) 所需的代价.
- \(s2[u]\) : 表示点 \(u\) 子树中的点的总权值.
那么第二部分的代价就为 \(v1[ft[u]]-v2[u]+dis(u,ft[u])*(s2[fa]-s2[u])\).
可以这样理解 :
\(v1[ft[u]]-v2[u]\) 表示将点 \(u\) 的子树的代价去除.
\(+dis(u,ft[u])*(s2[fa]-s2[u])\) 表示将到 \(ft[u]\) 的代价转为到 \(u\) 的代价.
由上可得, 求一个点的代价的时间复杂度为 : 在点分树上逐层向上的 \(\log n\) 乘上算 \(dis\) 的 \(\log n\), 总共为 \(O(\log^2 n)\).
再乘上之前的复杂度, 得到一次询问的复杂度为 \(O(20\log^3 n)\),
那么总复杂度为 \(O(20n\log^3 n)\) (因为 \(q\) 和 \(n\) 是相同规模的), 大概有 \(10^9\), 看起来就很不靠谱, 但是如果把用树剖求 \(lca\), 这个复杂度就跑不满, 再加上 \(6s\) 的时限, 还是可以过的, 并且洛谷上最优解总时间只有 \(500\) 多毫秒....
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=1e5+7;
const int __=2e5+7;
const int L=20;
const int inf=0x3f3f3f3f;
bool be;
int n,q,dep[_],sz[_],top[_],son[_],fat[_],rt,minx,ft[_],rtt[__]; // v1: to self v2: to father
ll v1[_],v2[_],s2[_],dis[_];
int lst[_],nxt[__],to[__],tot;
ll len[__];
bool vis[_];
bool en;
void add(int x,int y,ll w){ nxt[++tot]=lst[x]; to[tot]=y; len[tot]=w; lst[x]=tot; }
void dfs1(int u,int fa){
fat[u]=fa;
dep[u]=dep[fa]+1;
for(int i=lst[u];i;i=nxt[i]){
int v=to[i];
if(v==fa) continue;
dis[v]=dis[u]+len[i];
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs2(int u){
if(son[u]){
top[son[u]]=top[u];
dfs2(son[u]);
}
for(int i=lst[u];i;i=nxt[i])
if(!top[to[i]]){
top[to[i]]=to[i];
dfs2(to[i]);
}
}
int Lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fat[top[x]];
}
return dep[x]<=dep[y] ?x :y;
}
ll dist(int x,int y){ return dis[x]+dis[y]-2*dis[Lca(x,y)]; }
void g_rt(int u,int fa,int sum){
int maxn=0; sz[u]=1;
for(int i=lst[u];i;i=nxt[i]){
int v=to[i];
if(vis[v]||v==fa) continue;
g_rt(v,u,sum);
sz[u]+=sz[v];
maxn=max(maxn,sz[v]);
}
maxn=max(maxn,sum-sz[u]);
if(maxn<minx){ minx=maxn; rt=u; }
}
void init(int u,int lrt,int sum){
minx=inf;
g_rt(u,0,sz[u]<sz[lrt] ?sz[u] :sum-sz[lrt]);
sum=sz[u];
ft[rt]=lrt;
vis[rt]=1; u=rt;
for(int i=lst[u];i;i=nxt[i])
if(!vis[to[i]]){
init(to[i],u,sum);
rtt[i]=rt;
}
rt=u;
vis[rt]=0;
}
void modify(int x,ll w){
int u=x,fa=ft[u];
ll len;
while(fa){
len=dist(fa,x);
//printf("u: %d fa: %d len: %d\n",u,fa,len);
v1[fa]+=w*len;
v2[u]+=w*len;
s2[u]+=w;
u=ft[u]; fa=ft[u];
}
s2[u]+=w;
}
ll cst(int x){
int u=x,fa=ft[u];
ll len,res=v1[x];
while(fa){
len=dist(x,fa);
res+=v1[fa]-v2[u]+len*(s2[fa]-s2[u]);
u=ft[u]; fa=ft[u];
}
return res;
}
ll query(){
int u=rt;
ll ans=1e17;
while(u){
ans=min(ans,cst(u));
int x=0;
ll minx=1e17;
for(int i=lst[u];i;i=nxt[i]){
if(!rtt[i]) continue;
int v=to[i];
ll tmp=cst(v);
if(tmp<minx){ minx=tmp; x=rtt[i]; }
}
u=x;
}
return ans;
}
int main(){
//freopen("x.in","r",stdin);
//freopen("x.out","w",stdout);
cin>>n>>q;
int x,y; ll w;
for(int i=1;i<n;i++){
scanf("%d%d%lld",&x,&y,&w);
add(x,y,w);
add(y,x,w);
}
init(1,0,n);
dfs1(1,0);
top[1]=1;
dfs2(1);
for(int i=1;i<=q;i++){
scanf("%d%lld",&x,&w);
modify(x,w);
printf("%lld\n",query());
}
}