题意

树的结构不变,每个点有点权,每一条边有边权,有修改点权的操作,设xxx为树中一点.求∑idist(x,i)∗a[i]\sum_idist(x,i)*a[i]i∑​dist(x,i)∗a[i]的最小值

分析

我们把补给站叫做决策点,那么假设当前最优决策点为uuu.把uuu看作根节点,我们考虑将决策点从uuu转到儿子vvv,先假设sum[i]sum[i]sum[i]表示iii子树内点权之和,那么减少的代价就是sum[v]∗len(u,v)sum[v]*len(u,v)sum[v]∗len(u,v),增多的代价就为(sum[u]−sum[v])∗len(u,v)(sum[u]-sum[v])*len(u,v)(sum[u]−sum[v])∗len(u,v).所以说vvv比uuu优,当且仅当sum[v]∗len(u,v)>(sum[u]−sum[v])∗len(u,v)sum[v]*len(u,v)>(sum[u]-sum[v])*len(u,v)sum[v]∗len(u,v)>(sum[u]−sum[v])∗len(u,v)也就是2∗sum[v]>sum[u]2*sum[v]>sum[u]2∗sum[v]>sum[u]不难发现,满足这个式子的儿子至多有一个.那么一个暴力的想法就是每次询问从根开始,往儿子下面走,如果不存在儿子比自己更优,那么自己就是决策点,否则就进入比自己优的儿子的子树.

这样每次询问的时间复杂度看似是O(n)O(n)O(n)的.但是实际上是O(Maxdepth)O(Maxdepth)O(Maxdepth)的.于是我们就有了想法,点分治不就是将深度减小为O(logn)O(logn)O(logn)了吗?我们把点分治中每一个重心的父亲设为上一层的重心,就在O(nlogn)O(nlogn)O(nlogn)的时间内构造了一棵点分树,这棵树的深度是O(logn)O(logn)O(logn)的,那么我们只需要在每个重心上维护子树信息,就可以在O(logn)O(logn)O(logn)的时间内求出把一个点当作决策点的答案,因为可以证明(但我不会)的是一对点在点分树中的lcalcalca一定存在于它们在原树上的路径,那么他们间的距离乘以点权只需要利用在lcalcalca上维护的子树信息来计算,具体方法就是一个点在点分树上往根节点跑,在路径上统计就能求出它作为决策点的答案(具体之后看代码).

我们将树的深度减小到了O(logn)O(logn)O(logn),所以每次查询只需要从根节点(整棵树的重心)开始,看哪一个(原树中)儿子更优,如果优就往儿子对应的子树的重心上走.每一次查询时间复杂度是O(20log2n)O(20log^2n)O(20log2n),一个logloglog是要走的深度,一个logloglog是求对应的答案,202020是因为对于当前点uuu要计算所有儿子vvv作为决策点的值,题目中保证了一个点度数不超过202020.而且因为是一旦搜到比自己优的儿子就去那一坨子树的重心,一般是跑不满202020的.

再考虑修改,只用把自己在点分树中的祖先修改就行了,每一次时间复杂度O(logn)O(logn)O(logn)

实际上,每一次计算/修改都需要算两个点在原树上的距离,要求lcalcalca,可以用dfsdfsdfs序预处理+O(1)RMQ+O(1)RMQ+O(1)RMQ,从而整道题的时间复杂度为O(20nlog2n)O(20nlog^2n)O(20nlog2n),而我比较懒,求lcalcalca用的树剖,严格来说是O(20nlog3n)O(20nlog^3n)O(20nlog3n)的,但是树剖大多数时候达不到logloglog,而且202020也跑不满,那么就能过了…

其实这就是个暴力…优雅的暴力…

修改/计算具体看代码

CODE

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
template<typename T>inline void read(T &num) {
char ch; int flg = 1;
while((ch=getchar())<'0'||ch>'9')if(ch=='-')flg=-flg;
for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
num*=flg;
}
const int MAXN = 100005;
int n, q, fir[MAXN], cnt;
struct edge { int to, nxt, w; }e[MAXN<<1];
inline void add(int u, int v, int wt) {
e[cnt] = (edge){ v, fir[u], wt }, fir[u] = cnt++;
e[cnt] = (edge){ u, fir[v], wt }, fir[v] = cnt++;
}
int dis[MAXN], son[MAXN], sz[MAXN], top[MAXN], fa[MAXN], dep[MAXN];
void dfs(int u, int ff) {
dep[u] = dep[fa[u]=ff] + (sz[u]=1);
for(int i = fir[u], v; ~i; i = e[i].nxt)
if((v=e[i].to) != ff) {
dis[v] = dis[u] + e[i].w;
dfs(v, u), sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int tp) {
top[u] = tp;
if(son[u]) dfs2(son[u], tp);
for(int i = fir[u], v; ~i; i = e[i].nxt)
if((v=e[i].to) != fa[u] && v != son[u])
dfs2(v, v);
}
int lca(int u, int v) { //
while(top[u] != top[v]) {
if(dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
int dist(int u, int v) { return dis[u] + dis[v] - 2*dis[lca(u,v)]; }
int Fa[MAXN], size[MAXN], Size, Minr, root, info[MAXN], CNT;
struct EDGE { int to, nxt, rt; }E[MAXN];
inline void ADD(int u, int v, int rr) {
E[CNT] = (EDGE){ v, info[u], rr }, info[u] = CNT++;
}
bool vis[MAXN]; void Getrt(int u, int ff) {
size[u] = 1;
int ret = 0;
for(int i = fir[u], v; ~i; i = e[i].nxt)
if((v=e[i].to) != ff && !vis[v]) {
Getrt(v, u), size[u] += size[v];
ret = max(ret, size[v]);
}
ret = max(ret, Size-size[u]);
if(ret < Minr) Minr = ret, root = u;
}
void DFS(int u, int ff) {
vis[u] = 1; Fa[u] = ff; int totsize = Size;
for(int i = fir[u], v; ~i; i = e[i].nxt)
if(!vis[v=e[i].to]) {
Minr = n;
Size = size[v] > size[u] ? totsize - size[u] : size[v];
//此处不能直接用size[v],因为那是以1为根求出来的size
//这里问号语句判断size大小是在看u和v在1为根的树中谁是谁的儿子
//从而可以计算出以u为根时v的子树大小,自己YY一下...
Getrt(v, u);
ADD(u, v, root);
DFS(root, u);
}
} LL sum[MAXN]; //子树点数之和
LL sumd[MAXN]; //子树内的距离之和
LL sumf[MAXN]; //子树对父亲的贡献 inline void Modify(int u, int val) {
sum[u] += val;
for(int i = u; Fa[i]; i = Fa[i]) {
int len = dist(u, Fa[i]); //注意这里是到u的距离
sum[Fa[i]] += val; //累加点数
sumd[Fa[i]] += 1ll * val * len; //累加距离之和
sumf[i] += 1ll * val * len; //累加i这个子树对i父亲的贡献(记在i上)
}
} inline LL Count(int u) { //多理解一会,画画图多YY下
LL res = sumd[u]; //自己子树的距离之和
for(int i = u; Fa[i]; i = Fa[i]) {
int len = dist(u, Fa[i]); //注意这里是到u的距离
res += (sum[Fa[i]]-sum[i]) * len; //Fa[i]的其他子树的点数 * 这一条路径的长度
res += (sumd[Fa[i]]-sumf[i]); //Fa[i]的其他子树的点到Fa[i]的距离之和
//上面两个统计都必须要减去这个子树的贡献,否则算重了
}
return res;
}
LL Query(int u) {
LL tmp = Count(u);
for(int i = info[u]; ~i; i = E[i].nxt)
if(Count(E[i].to) < tmp) return Query(E[i].rt); //儿子比自己优就去对应的重心
return tmp;
}
int main () {
memset(fir, -1, sizeof fir);
memset(info, -1, sizeof info);
read(n), read(q);
for(int i = 1, x, y, z; i < n; ++i)
read(x), read(y), read(z), add(x, y, z);
dfs(1, 0), dfs2(1, 1);
Size = Minr = n;
Getrt(1, 0); //先求一次重心
int RT = root; //作为根
DFS(root, 0);
int x, y;
while(q--) {
read(x), read(y);
Modify(x, y);
printf("%lld\n", Query(RT));
}
}

BZOJ 3924 / Luogu P3345 [ZJOI2015]幻想乡战略游戏 (动态点分治/点分树)的更多相关文章

  1. P3345 [ZJOI2015]幻想乡战略游戏 动态点分治

    \(\color{#0066ff}{ 题目描述 }\) 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越 ...

  2. 洛谷P3345 [ZJOI2015]幻想乡战略游戏 [动态点分治]

    传送门 调了两个小时,终于过了-- 凭啥人家代码80行我180行啊!!! 谁叫你大括号换行 谁叫你写缺省源 思路 显然,补给点所在的位置就是这棵树的带权重心. 考虑size已知时如何找重心:一开始设答 ...

  3. [ZJOI2015]幻想乡战略游戏——动态点分治

    [ZJOI2015]幻想乡战略游戏 带修改下,边点都带权的重心 随着变动的过程中,一些子树内的点经过会经过一些公共边.考虑能不能对这样的子树一起统计. 把树上贡献分块. 考虑点分治算法 不妨先把题目简 ...

  4. bzoj 3924 [Zjoi2015]幻想乡战略游戏——动态点分治(暴力移动找重心)

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3924 度数只有20,所以从一个点暴力枚举其出边,就能知道往哪个方向走. 知道方向之后直接走到 ...

  5. [BZOJ3924][ZJOI2015]幻想乡战略游戏(动态点分治)

    题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打 ...

  6. 【bzoj3924】[Zjoi2015]幻想乡战略游戏 动态点分治

    题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打 ...

  7. ZJOI2015 幻想乡战略游戏 动态点分治_树链剖分_未调完

    Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来, ...

  8. BZOJ 3924: [Zjoi2015]幻想乡战略游戏(动态点分治)

    这种动态点分治嘛,GDKOI时听打到了,也有同学讲到了,所以印象比较深刻也就想出来了,然后就在实现方面卡了好久= = 不得不说CLJ说得真的太简单了,实现方面根本没提. 首先我们可以先用树分治构建出这 ...

  9. 洛谷 P3345 [ZJOI2015]幻想乡战略游戏 解题报告

    P3345 [ZJOI2015]幻想乡战略游戏 题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做 ...

随机推荐

  1. 【leetcode算法-简单】14. 最长公共前缀

    [题目描述] 编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","fl ...

  2. Netty源码剖析-接受数据

    参考文献:极客时间傅健老师的<Netty源码剖析与实战>Talk is cheap.show me the code! ----主线:worker thread ①多路复用器(Select ...

  3. 【规律】Growing Rectangular Spiral

    Growing Rectangular Spiral 题目描述 A growing rectangular spiral is a connected sequence of straightline ...

  4. varchar、nvarchar

    Unicode字符集就是为了解决字符集这种不兼容的问题而产生的,它所有的字符都用两个字节表示,即英文字符也是用两个字节表示. NCHAR.NVARCHAR.NTEXT.这三种从名字上看比前面三种多了个 ...

  5. 命令行发送SMTP协议邮件(163邮箱)

    这里我们用163邮箱为例子,借助命令行发送smtp邮件 1.连接服务器 在终端上输入:telnet smtp.163.com 25 回车,然后就连接了服务器的25端口,成功会输出 220 163.co ...

  6. 高性能网站建设之 MS Sql Server数据库分区

    什么是数据库分区?数据库分区是一种对表的横向分割,Sql server 2005企业版和之后的Sql server版本才提供这种技术,这种对表的横向分割不同于2000中的表分割,它对访问用户是透明的, ...

  7. 1、windows安装npm教程 --参考自https://www.cnblogs.com/jianguo221/p/11487532.html

    windows安装npm教程   1.在使用之前,先类掌握3个东西,明白它们是用来干什么的: npm:  nodejs 下的包管理器. webpack: 它主要用途是通过CommonJS 的语法把所有 ...

  8. H5存储方式

    数据存储 var arr = [0, 1, 1, 1]; //存储,IE6~7 cookie 其他浏览器HTML5本地存储 if (window.localStorage) { localStorag ...

  9. String s=new String("xyz");创建了几个String Object?二者之前的区别是什么?

    两个.第一个对象是字符串常量"xyz",第二个对象是new String("xyz")的时候产生的,在堆中分配内存给这个对象,只不过这个对象的内容是指向字符串常 ...

  10. impala 建表时报错,不支持中文

    1.错误信息 (1366, "Incorrect string value: '\\xE6\\x8E\\x88\\xE6\\x9D\\x83...' for column 'search' ...