洛谷P3345 [ZJOI2015]幻想乡战略游戏(动态点分治,树的重心,二分查找,Tarjan-LCA,树上差分)
动态点分治小白,光是因为思路不清晰就耗费了不知道多少时间去gang这题,所以还是来理理思路吧。
一个树\(T\)里面\(\sum\limits_{v\in T} D_vdist(u,v)\)取到最小值的\(u\)我们可以称作带权重心。类似重心各种性质的证明过程,我们不难证出这样的点顶多只有两个。
如果\(e\)都是正数的话比较好做。类比重心性质,新带权重心一定在原带权重心和修改点之间的路径上,可以直接像首都(蒟蒻题解)那样用LCT维护以带权重心为根的树,修改时提出链二分查找出新带权重心即可。
可是\(e\)会是负数啊!这时候,新带权重心一定会有远离修改点的趋势。但是子树太多了,我们根本不能快速知道往哪里移。我们只能暴力判断,如果当前决策点是\(x\),\(y\)与\(x\)有边相连,那么假如把补给站移过去,显然答案会减去\(y\)一侧子树的\(w×\sum\limits_{v\in Y} D_v\)(\(w\)为当前边权),加上\(x\)一侧子树的\(w×\sum\limits_{v\in X} D_v\)。那么当\(\sum\limits_{v\in Y} D_v\geq\sum\limits_{v\in X} D_v\)也就是\(2\sum\limits_{v\in Y} D_v\geq\sum\limits_{v\in T} D_v\)时我们当然可以更改决策点,然后继续寻找更优的点,直到找不到为止。只可惜这样是\(O(n^2)\)的。
怎么快速找呢?我们可以想到动态点分治,点分树的高度是\(\log\)级别的,把它建好后,维护每个以\(u\)为根的点分子树的\(D\)之和\(s_u\)。根据上式判断,如果当前点的某一个有边相邻的点更优,我们直接把决策点改成该点所在的点分子树的根节点!由于每个点度数很小,我们甚至可以暴力for
一遍判断。这也是像首都一样二分查找重心。
但是,具体实现起来又有不少问题。
首先,点分树保证了高度,却保证不了信息的完整性,每个节点只能维护该节点所在点分子树的信息。而上面那个式子需要维护原树的子树\(\sum D\)。试想一下当我们在点分树里从根向下跳了\(k\)层,那么当前的子树还会通过边连接上\(O(k)\)个外部子树。如果for
每一条边的时候都要判断每一个外部子树在边的哪一侧,岂不是很费劲?
聪明的做法,可以理解为缩点,是在每次找到更优点之后,跳向点分子树\(v\)之前,把更优点的\(D\)临时加上\(s_u-s_v\)即外部子树的\(\sum D\)。当然,注意区分点分树和原树,更优点并不一定是当前点的子节点,所以更优点的点分树上的祖先的\(s\)也要更新。用递归实现,等到找出了带权重心,回溯了之后,再把它还原。稍稍脑补一下,是不是很轻松地消除了外部子树的影响?
其次,找出了重心,我们还要统计答案。仔细想想,这个答案实在不方便也没必要丢到别的数据结构(线段树,LCT什么的)动态维护了。把点分树建出来了,还不好好使用它?我们显然要维护每个以\(x\)为根点分子树中的\(\sum\limits_{v\in X} D_vdist(x,v)\),记为\(tot_x\)。设统计\(x\)的答案,首先\(tot_x\)直接算进去了。但是如果加上\(x\)祖先的\(tot\),就会有多余的,多余出\(x\)所在点分子树的贡献。这时候来一波树上差分,再记\(tof_x\)表示\(\sum\limits_{v\in X} D_vdist(fa_x,v)\)(\(fa_x\)为\(x\)点分树上的父亲),每加上\(tot_{fa_x}\)就减掉\(tof_x\)就好啦。
思路至此,点分树里的三个变量怎么维护也不是大问题了。当\(D_x\)加上\(e\)的时候,更新点分树上\(x\)及其祖先,设当前修改\(y\),那么\(s_y\)加上\(e\)、\(tot_{fa_y}\)和\(tof_y\)都加上\(e×dist(x,{fa_y})\)就是显然的了。那么我们还要预处理出每个点到点分树上每个祖先的距离,这个可以离线,为什么没人写Tarjan求LCA呢?(话说蒟蒻也是第一次写)把询问丢进去,以\(1\)为根求每个点的带权深度\(dep\),处理出LCA之后直接差分,\(dist(x,y)=dep_x+dep_y-2dep_{LCA}\),这个大家都会吧。
思路实在不清晰就看看代码吧。话说动态点分治的代码都短不了吧。。。。。。不过Tarjan写起来方便不少,蒟蒻没过分压行也只有90多行。
#include<cstdio>
#include<cstring>
#define LL long long
#define RG register
#define R RG int
#define G c=getchar()
const int N=1e5+9,M=2e5+9,L=2e6;
namespace E{int he[N],ne[M],to[M];}//原树
namespace T{int he[N],ne[N],to[N];}//点分树
namespace Q{int he[L],ne[L<<1],to[L<<1];}//LCA询问
using namespace E;//封了namespace确实清楚多了
int n,p,rt,w[M],s[N],mx[N],h[N],dep[N],fa[N],top[N],dis[N][20],*at[L<<1];
LL tot[N],tof[N];//该开longlong的别忘记
bool vis[N];
inline int in(){
RG char G;RG bool f=0;
while(c<'-')G;
if(c=='-')f=1,G;
R x=c&15;G;
while(c>'-')x*=10,x+=c&15,G;
return f?-x:x;
}
inline void max(R&x,R y){if(x<y)x=y;}
void getrt(R x){//建点分树求重心
vis[x]=1;s[x]=1;mx[x]=0;
for(R y,i=he[x];i;i=ne[i]){
if(vis[y=to[i]])continue;
getrt(y);
s[x]+=s[y];max(mx[x],s[y]);
}
max(mx[x],n-s[x]);
if(mx[rt]>mx[x])rt=x;
vis[x]=0;
}
int div(R x){//递归建树
getrt(x);vis[x=rt]=1;rt=0;
for(R t=n,y,i=he[x];i;i=ne[i]){
if(vis[y=to[i]])continue;
n=s[x]>s[y]?s[y]:t-s[x];
T::ne[++p]=T::he[x];T::he[x]=p;
fa[T::to[T::he[x]]=div(top[p]=y)]=x;
}//小心递归后p变了,写T::to[p]会出事
return x;
}
int geth(R x){return x==h[x]?x:h[x]=geth(h[x]);}//路径压缩
void tarjan(R x){//预处理dist
vis[x]=1;
for(R y,i=he[x];i;i=ne[i]){
if(vis[y=to[i]])continue;
dep[y]=dep[x]+w[i];
tarjan(y);h[y]=x;
}
for(R y,i=Q::he[x];i;i=Q::ne[i])
if(vis[y=Q::to[i]])
*at[i]=dep[x]+dep[y]-(dep[geth(y)]<<1);//差分
}
LL find(R x){
R i;
for(i=T::he[x];i&&s[T::to[i]]<<1<s[x];i=T::ne[i]);//找更优点
if(!i)return x;//找不到的话当前点就是最优点
R y,del=s[x]-s[T::to[i]];
for(y=top[i];y!=x;y=fa[y])s[y]+=del;//缩点
R ret=find(T::to[i]);
for(y=top[i];y!=x;y=fa[y])s[y]-=del;//还原
return ret;
}
int main(){
mx[0]=1e9;//记得给初值
R n=::n=in(),q=in(),x,y,v,i;
for(i=1;i<n;++i){
x=in();y=in();
ne[++p]=he[x];to[he[x]=p]=y;
ne[++p]=he[y];to[he[y]=p]=x;
w[p]=w[p-1]=in();
}
p=0;rt=div(1);p=0;
for(x=1;x<=n;++x)
for(i=0,y=fa[h[x]=x];y;y=fa[y],++i){
Q::ne[++p]=Q::he[x];Q::to[Q::he[x]=p]=y;
Q::ne[++p]=Q::he[y];Q::to[Q::he[y]=p]=x;
at[p]=at[p-1]=&dis[x][i];//搞个指针,Tarjan的时候直接把值放进去
}
memset(vis,0,n+1);memset(s,0,(n+1)<<2);//之前用过要清空
tarjan(1);
RG LL t;
while(q--){
x=y=in();s[rt]+=v=in();
for(i=0;y!=rt;++i)//维护
t=(LL)dis[x][i]*v,s[y]+=v,tof[y]+=t,tot[y=fa[y]]+=t;
t=tot[x=y=find(rt)];
for(i=0;y!=rt;++i)//算答案
t+=(LL)dis[x][i]*(s[fa[y]]-s[y])+tot[fa[y]]-tof[y],y=fa[y];
printf("%lld\n",t);
}
return 0;
}
洛谷P3345 [ZJOI2015]幻想乡战略游戏(动态点分治,树的重心,二分查找,Tarjan-LCA,树上差分)的更多相关文章
- 洛谷P3345 [ZJOI2015]幻想乡战略游戏 [动态点分治]
传送门 调了两个小时,终于过了-- 凭啥人家代码80行我180行啊!!! 谁叫你大括号换行 谁叫你写缺省源 思路 显然,补给点所在的位置就是这棵树的带权重心. 考虑size已知时如何找重心:一开始设答 ...
- 2018.08.28 洛谷P3345 [ZJOI2015]幻想乡战略游戏(点分树)
传送门 题目就是要求维护带权重心. 因此破题的关键点自然就是带权重心的性质. 这时发现直接找带权重心是O(n)的,考虑优化方案. 发现点分树的树高是logn级别的,并且对于以u为根的树,带权重心要么就 ...
- 洛谷 P3345 [ZJOI2015]幻想乡战略游戏 解题报告
P3345 [ZJOI2015]幻想乡战略游戏 题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做 ...
- P3345 [ZJOI2015]幻想乡战略游戏 动态点分治
\(\color{#0066ff}{ 题目描述 }\) 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越 ...
- BZOJ 3924 / Luogu P3345 [ZJOI2015]幻想乡战略游戏 (动态点分治/点分树)
题意 树的结构不变,每个点有点权,每一条边有边权,有修改点权的操作,设xxx为树中一点.求∑idist(x,i)∗a[i]\sum_idist(x,i)*a[i]i∑dist(x,i)∗a[i]的最 ...
- [ZJOI2015]幻想乡战略游戏——动态点分治
[ZJOI2015]幻想乡战略游戏 带修改下,边点都带权的重心 随着变动的过程中,一些子树内的点经过会经过一些公共边.考虑能不能对这样的子树一起统计. 把树上贡献分块. 考虑点分治算法 不妨先把题目简 ...
- [BZOJ3924][ZJOI2015]幻想乡战略游戏(动态点分治)
题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打 ...
- 【bzoj3924】[Zjoi2015]幻想乡战略游戏 动态点分治
题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打 ...
- ZJOI2015 幻想乡战略游戏 动态点分治_树链剖分_未调完
Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来, ...
随机推荐
- (原创)odoo解决方案---接收以及回复外部邮件
关于我的那篇"odoo邮件配置那些事儿"中提到的用户接收外部与业务无关邮件的问题,现已形成解决方案,有需要的朋友可以给发email,价格好商量,呵呵 直接贴图了 1.用户绑定 图1 ...
- (转)Ubuntu无法找到add-apt-repository问题的解决方法
原文 网上查了一下资料,原来是需要 python-software-properties 于是 apt-get install python-software-properties 除此之外还要安装 ...
- [Oracle][OnlineREDO]数据库无法启动时的对应策略:
[Oracle][OnlineREDO]数据库无法启动时的对应策略: 1. Start with mount. SQL> conn / as sysdba SQL> startup mo ...
- 【php增删改查实例】第十二节 - 数据删除功能
1.单条数据删除 思路:首先,需要也只能允许用户勾选一条数据,然后弹出一个确认框,问用户是否真的要删除?如果是,就把ID传递到PHP,然后写一个delete语句,通过ID去删除即可. 画好了按钮之后, ...
- python语言程序设计5
1, 评估函数eval() 去掉参数最外侧引号并执行余下语句的函数. 比如eval("1"),经过运行可以得到数字 1 去得是单双引号,不是括号哦.. 广泛来说,能将任何字符串的形 ...
- Roslyn入门(一)-C#语法分析
演示环境 Visual Studio 2017 .NET Compiler Platform SDK 简介 今天,Visual Basic和C#编译器是黑盒子:输入文本然后输出字节,编译管道的中间阶段 ...
- Flutter - Json序列化
这个问题,FlutterChina小组已经说明的非常清楚易懂了. 详见https://flutterchina.club/json/
- linux下安装redis组件报错-gcc报错
报错如图: 1.解决办法 先安装gcc插件.删除redis解压后文件.重新解压
- D. Cutting Out
---恢复内容开始--- 链接 [https://codeforces.com/contest/1077/problem/D] 题意 给你n,k,n个数,找出长度为k,的子串(不需连续),使得该子串数 ...
- 兼容IE-FireFox-Chrome的背景音乐播放
以music目录下的kn.mp3文件为例: <bgsound src="music/kn.mp3" loop="-1"/> <audio sr ...