点分治Day2 动态树分治
蒟蒻Ez3real冬令营爆炸之后滚回来更新blog...
我们看一道题
bzoj3924
ZJOI2015D1T1 幻想乡战略游戏
给一棵$n$个点的树$(n \leqslant 150000)$ 点上有点权 边上有边权
每个点度数不大于$20$
你需要放置一个补给站 补给站供给某个点的代价等于它们之间路径长度 $\cdot$ 那个点的点权
供给整张图的代价为供给每个点的代价之和
即:如供给站在$u$,每个点的点权为 $d_{v_{i}}$
则总代价为 $$ \sum_{i=1}^{n} d_{v_{i}} \cdot dist(u,v_{i})$$
每次操作更改一个点的点权
每次更改后可以选一个位置放补给站 你需要求出供给整张图代价最小值
(每次改点权 询问树上带权重心)
如果没有修改操作,就是我们曾经研究过的树上路径问题,可以用点分治水过去
现在带修改,于是我们考虑:动态树分治
动态树分治又称点分树 是把树用数据结构维护的一种方法
与点分治一样地 我们找到每个子树的重心来构建这颗树
每层重心构成一个树形结构
我们在这个树形结构上跑数据结构就可以咯
具体地
首先我们可以先用树分治构建出这棵树的分治树,也就是把这棵树的重心作为根节点然后子树为他的子树的重心这样递归下去,然后每个节点存的是其子树的信息。
对于每个节点我们保存这个子树的$d_{v}$的总和已经把该节点作为点的答案值
这样对于修改能在$log_2n$的时间内解决
寻找答案的时候,我们可以发现,如果现在节点的子树$\sum d_{v_{i}}$大于总节点,那么向那个方向过去一定比原方案好
我们先从根节点开始,若发现答案在某棵子树时,我们考虑如何使其儿子节点的答案转变为整个树的答案,可以发现把除这个子树外的所有节点可以缩成一个节点并连在这棵子树上,然后就可以一直这样做下去,找到操作之后再把这些撤销
因此还是得维护一些奇奇怪怪的东西,但打出来还是挺短的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define LL long long const int maxn = 400010; using namespace std; int m,n; int first[maxn],to[maxn],next[maxn],val[maxn],cnt; inline void add(int u,int v,int w) { to[++cnt]=v; val[cnt]=w; next[cnt]=first[u]; first[u]=cnt; } int dep[maxn],dis[maxn],fat[maxn][22],s[maxn]; int id[maxn],ip[maxn],top; void dfs(int u,int fa) { s[++top]=u; if(!id[u])id[u]=top; dep[top]=dep[ip[fa]]+1;ip[u]=top; for(int i=first[u];i;i=next[i]) { int v=to[i]; if(v==fa)continue; dis[v]=dis[u]+val[i]; dfs(v,u);s[++top]=u;dep[top]=dep[ip[fa]+1]; } } void make() { for(int i=1;i<=top;i++) fat[i][0]=i; for(int j=1;j<=18;j++) for(int i=1;i<=top;i++) if(i+(1<<j)-1<=top) { int x=fat[i][j-1],y=fat[i+(1<<j-1)][j-1]; if(dep[fat[i][j-1]]<dep[fat[i+(1<<j-1)][j-1]]) fat[i][j]=fat[i][j-1]; else fat[i][j]=fat[i+(1<<j-1)][j-1]; } } inline int query(int l,int r) { int len=r-l+1,k=0; for(k=0;1<<k+1<=len;k++); if(dep[fat[l][k]]<dep[fat[r-(1<<k)+1][k]])return fat[l][k]; else return fat[r-(1<<k)+1][k]; } inline int lca(int u,int v) { if(id[u]>id[v]) swap(u,v); return s[query(id[u],id[v])]; } inline int caldis(int u,int v) { int LCA=lca(u,v); return dis[u]+dis[v]-2*dis[LCA]; } int rt,sum,f[maxn],size[maxn],vis[maxn]; inline void GetRT(int x,int fa) { size[x]=1;f[x]=0; for(int i=first[x];i;i=next[i]) { if(to[i]==fa || vis[to[i]])continue; GetRT(to[i],x);size[x]+=size[to[i]]; f[x]=max(f[x],size[to[i]]); } f[x]=max(f[x],sum-size[x]); if(f[x]<f[rt])rt=x; } int ret,dv[maxn],par[maxn]; LL ans[maxn],anss[maxn],summ[maxn]; inline void work(int x) { vis[x]=1;summ[x]=ret; for(int i=first[x];i;i=next[i]) { if(vis[to[i]])continue; rt=0,sum=size[to[i]]; GetRT(to[i],0); par[rt]=x;work(rt); } } LL cal(int u) { LL ret=ans[u]; for(int i=u;par[i];i=par[i]) { LL delt=caldis(par[i],u); ret+=(ans[par[i]]-anss[i]); ret+=delt*(summ[par[i]]-summ[i]); } return ret; } LL update(int u,int va) { summ[u]+=va; for(int i=u;par[i];i=par[i]) { LL di=caldis(par[i],u); summ[par[i]]+=va; anss[i]+=va*di; ans[par[i]]+=va*di; } } int last=1; LL query(int u) { LL ka=cal(u); for(int i=first[u];i;i=next[i]) { LL tmp=cal(to[i]); if(tmp < ka)return query(to[i]); } last=u; return ka; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w),add(v,u,w); }top=0,dfs(1,0); //cout<<top<<endl; make();sum=f[0]=n;GetRT(1,0); work(rt); for(int i=1;i<=m;i++) { int a,b; scanf("%d%d",&a,&b); update(a,b); printf("%lld\n",query(last)); } }
习题:bzoj3924 bzoj1095 bzoj4012