大概有整整一个月没更博客了 ……

4 月为省选爆肝了一个月,最后压线进 B 队,也算给 NOIP2018 翻车到 316 分压线省一这个折磨了五个月的 debuff 画上了一个不算太差的句号。结果省选后技能点全点到红警上了,OI 迅速变菜,GG 。

题目:

洛谷 3345

分析:

为什么我觉得这题网上大部分题解都讲的很麻烦,看了一上午还没看懂,有一种被拐到沟里的感觉 …… 我这个思路自认为比较好理解。

先考虑一个比较弱的问题:把原问题的「寻找最优补给站」改为「每次询问钦定一个补给站,求此时的花费」。

这很明显是个点分树裸题,建议先充分理解 BZOJ3730 震波(此处应有本人博客链接,无限咕咕中)。非常类似于震波的做法,对每个结点 \(u\) 维护 \(sum_u\) 、 \(sumd_u\) 、 \(sumf_u\) 、 \(sumdf_u\) ,分别表示点分树上结点 \(u\) 「管辖范围」(可以理解为点分治时 get_path 函数遍历的那棵子树)中的点权之和、「点权乘深度(到根节点的边权之和,下同)」之和、对点分树上父节点贡献的点权之和、对点分树上父节点贡献的「点权乘深度」之和。

P.S. 写博客的时候突然发现 \(sum_u\) 和 \(sumf_u\) 其实是同一个东西,但我还是要为了「对称美」以及套震波的板子把它们分开了(滑稽)。

代码如下,如果充分理解了 BZOJ3730 的做法应该很好懂。代码中用 \(sum_{u+n}\) 和 \(sumd_{u+n}\) 表示 \(sumf_u\) 和 \(sumdf_u\) 。用 ST 表求 LCA ,单次修改和查询都是 \(O\left(\log n\right)\) 的。

void modify(const int u, const int x)
{using LCA::get_dis;int tmp = u;wtot += x;d[u] += x;sum[u] += x;while (fa[tmp]){int d = get_dis(u, fa[tmp]);sum[fa[tmp]] += x;sum[tmp + n] += x;sumd[fa[tmp]] += (ll)x * d;sumd[tmp + n] += (ll)x * d;tmp = fa[tmp];}
}
ll query(const int u)
{using LCA::get_dis;int tmp = u;ll ans = sumd[u];while (fa[tmp]){int d = get_dis(u, fa[tmp]);ans += sumd[fa[tmp]] - sumd[tmp + n] + (ll)d * (sum[fa[tmp]] - sum[tmp + n]);tmp = fa[tmp];}return ans;
}

暂时忘掉点分树(现在它的作用只是在 \(O(\log n)\) 的时间内求把补给站设在某个点的答案),只想原树,考虑一种贪心的做法:先随便站在一个点上,称为 \(u\) 。\(v\) 是一个与 \(u\) 的一个直接相连的结点(以下称为「儿子」)。如果 \(v\) 的答案比 \(u\) 更优,则从 \(u\) 走到 \(v\) 。如此往复,最终到一个无法再走的点,那么这个点就是最优点。口胡的不严谨证明如下:

用 \(sum_v\) 表示以 \(u\) 为根时 \(v\) 子树中点的点权之和, \(tot\) 表示全部 \(n\) 个点的点权之和,\(w\) 是边 \((u,v)\) 的权。那么,从 \(u\) 走到 \(v\) 后 \(v\) 子树中的所有点 \(p\) 的贡献减少 \(w\cdot d_p\) ,\(v\) 子树外的所有点 \(q\) 的贡献增加 \(w\cdot d_q\) ,那么答案变化量 \(\Delta=w\cdot (tot-sum_v)-w\cdot sum_v=w\cdot (tot-2sum_v)\) 。也就是说,只有当 \(2sum_v>tot\) ,答案才会减少,即 \(v\) 的答案比 \(u\) 更优。很明显,\(u\) 最多只能有一个儿子 \(v\) 满足 \(2sum_v>tot\) ,所以如果能移动,一定只有唯一的一种移动方案。并且,如果 \(2sum_v>tot\) ,则 \(2(tot-sum_v)\) (即以 \(v\) 为根时的 \(sum_u\) 的两倍)一定不大于 \(tot\) ,所以不可能往回移动。综上,这样一定能找到最优解。

如果专门造数据卡,上述贪心每次最多能走 \(n-1\) 步(考虑一条长链,中间全是 \(0\) ,两端轮流在 \(1\) 和 \(0\) 之间切换,最优解轮流出现在两个端点上),单次修改最坏 \(O(n\log n)\) ,会 TLE 。然而,这个贪心给我们一个重要的启示:对于相邻两点 \(u\) 和 \(v\) ,如果 \(u\) 比 \(v\) 优,那么答案 一定在 \(v\) 的子树中 (以 \(u\) 为树根)。换句话说,就是如果断掉边 \((u,v)\) ,则 答案一定在 \(v\) 所在的连通块中 。也就是说,我即使现在不走到 \(v\) ,只要走到(更形象地说,「跳到」) \(v\) 的子树中任意一点,也都能保证最终找到最优解。

那么我们每次不是走到 \(v\) ,而是走到 \(v\) 这棵子树的「重心」。从点分树的角度来说,记 \(near_v\) 表示从 \(v\) 的点分树父亲 \(u\) 到 \(v\) 的原树路径上除了 \(u\) 以外的第一个点。一开始站在根上,从 \(u\) 走到 \(v\) 的条件是 \(near_v\) 比 \(u\) 更优,最终无路可走了就是答案。由于点分树深度是 \(O(\log n)\) ,每次最多走深度步,每走一步要 \(O(\log n)\) 查询若干个点的答案,所以单次查询时间复杂度为 \(O(\log^2 n)\) (由于要遍历所有儿子,所以要乘上最大度数 \(20\) 的常数)。

代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <vector>
using namespace std;namespace zyt
{template<typename T>inline bool read(T &x){char c;bool f = false;x = 0;doc = getchar();while (c != EOF && c != '-' && !isdigit(c));if (c == EOF)return false;if (c == '-')f = true, c = getchar();dox = x * 10 + c - '0', c = getchar();while (isdigit(c));if (f)x = -x;return true;}template<typename T>inline void write(T x){static char buf[20];char *pos = buf;if (x < 0)putchar(' '), x = -x;do*pos++ = x % 10 + '0';while (x /= 10);while (pos > buf)putchar(*--pos);}typedef long long ll;const int N = 1e5 + 10, B = 20, INF = 0x3f3f3f3f;struct edge{int to, w, next;}e[N << 1];int n, head[N], ecnt, d[N];void add(const int a, const int b, const int c){e[ecnt] = (edge){b, c, head[a]}, head[a] = ecnt++;}namespace LCA{int dis[N], dfn[N], euler[N << 1], dfncnt;namespace ST{int lg2[N << 1], st[B][N << 1];const int *w;int min(const int a, const int b){return w[a] < w[b] ? a : b;}void build(const int *_w, const int n){w = _w;int tmp = 0;for (int i = 1; i <= n; i++){lg2[i] = tmp;if (i == (1 << (tmp + 1)))++tmp;}for (int i = 1; i <= n; i++)st[0][i] = i;for (int i = 1; i < B; i++)for (int j = 1; j + (1 << i) - 1 <= n; j++)st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);}int query(const int l, const int r){int len = lg2[r - l + 1];return min(st[len][l], st[len][r - (1 << len) + 1]);}}void dfs(const int u, const int f){dfn[u] = ++dfncnt;euler[dfncnt] = u;for (int i = head[u]; ~i; i = e[i].next){int v = e[i].to;if (v == f)continue;dis[v] = dis[u] + e[i].w;dfs(v, u);euler[++dfncnt] = u;}}void init(){dfncnt = 0;dfs(1, 0);ST::build(euler, dfncnt);}int lca(const int a, const int b){return euler[ST::query(min(dfn[a], dfn[b]), max(dfn[a], dfn[b]))];}int get_dis(const int a, const int b){return dis[a] + dis[b] - (dis[lca(a, b)] << 1);}}namespace Point_Divide_Tree{int f[N], near[N], rot, size[N], tot, fa[N];ll sum[N << 1], sumd[N << 1], wtot;bool vis[N];vector<int> g[N];void find_rot(const int u, const int fa){size[u] = 1, f[u] = 0;for (int i = head[u]; ~i; i = e[i].next){int v = e[i].to;if (vis[v] || v == fa)continue;find_rot(v, u);size[u] += size[v];f[u] = max(f[u], size[v]);}f[u] = max(f[u], tot - size[u]);if (f[u] < f[rot])rot = u;}int get_size(const int u, const int f){int ans = 1;for (int i = head[u]; ~i; i = e[i].next){int v = e[i].to;if (v == f || vis[v])continue;ans += get_size(v, u);}return ans;}void solve(const int u){vis[u] = true;for (int i = head[u]; ~i; i = e[i].next){int v = e[i].to;if (vis[v])continue;tot = get_size(v, u);f[0] = INF, rot = 0;find_rot(v, u);fa[rot] = u, g[u].push_back(rot);near[rot] = v;solve(rot);}}void modify(const int u, const int x){using LCA::get_dis;int tmp = u;wtot += x;d[u] += x;sum[u] += x;while (fa[tmp]){int d = get_dis(u, fa[tmp]);sum[fa[tmp]] += x;sum[tmp + n] += x;sumd[fa[tmp]] += (ll)x * d;sumd[tmp + n] += (ll)x * d;tmp = fa[tmp];}}ll query(const int u){using LCA::get_dis;int tmp = u;ll ans = sumd[u];while (fa[tmp]){int d = get_dis(u, fa[tmp]);ans += sumd[fa[tmp]] - sumd[tmp + n] + (ll)d * (sum[fa[tmp]] - sum[tmp + n]);tmp = fa[tmp];}return ans;}ll find(const int u){ll now = query(u);for (vector<int>::iterator it = g[u].begin(); it != g[u].end(); it++){int v = *it;if (query(near[v]) < now)return find(v);}return now;}}int work(){using namespace Point_Divide_Tree;int q;read(n), read(q);memset(head, -1, sizeof(int[n + 1]));for (int i = 1; i < n; i++){int a, b, c;read(a), read(b), read(c);add(a, b, c), add(b, a, c);}LCA::init();tot = n;f[rot = 0] = INF;find_rot(1, 0);int root = rot;solve(rot);while (q--){int u, e;read(u), read(e);modify(u, e);write(find(root)), putchar('\n');}return 0;}
}
int main()
{return zyt::work();
}

转载于:https://www.cnblogs.com/zyt1253679098/p/10835943.html

【洛谷3345_BZOJ3924】[ZJOI2015]幻想乡战略游戏(点分树)相关推荐

  1. P3345 [ZJOI2015]幻想乡战略游戏

    P3345 [ZJOI2015]幻想乡战略游戏 带修改带权重心 这是经典的树上寻找关键点的题目,我们使用点分治处理这个问题,因为点分治的特性,就相当于在树上二分了.但是这与倍增不同,倍增只是在链上二分 ...

  2. 【BZOJ3924】[Zjoi2015]幻想乡战略游戏 动态树分治

    [BZOJ3924][Zjoi2015]幻想乡战略游戏 Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网 ...

  3. [ZJOI2015] 幻想乡战略游戏——树链剖分

    [ZJOI2015]幻想乡战略游戏 题解 由于所有边的边权是正整数,所以可以发现任何时刻每个点的答案是从最优的点往周围递增的.如果有平台,那么一定是在最优点的连通块那儿. 我们先考虑如何快速求每个点的 ...

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

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

  5. luogu_P3345[zjoi2015]幻想乡战略游戏

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

  6. Bzoj3924 [Zjoi2015]幻想乡战略游戏

    Time Limit: 100 Sec  Memory Limit: 256 MB Submit: 817  Solved: 376 Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏 ...

  7. [zjoi2015]幻想乡战略游戏

    前言 略略略 题目相关 链接 题目大意 给出一棵树,每次修改一个点的权值,维护一个带权重心 啥是带权重心? 设点iii的值为ViV_iVi​我们要选一个点uuu,每个点对应一个值: ∑v=1nVv∗d ...

  8. BZOJ3924 : [Zjoi2015]幻想乡战略游戏

    对于一个点,要求出它到所有点的带权距离和,只需记录下树分治的结构然后查询即可. 修改$O(\log n)$,查询$O(\log n)$. 到所有点带权距离和最小的点显然是这棵树的带权重心. 以1号点为 ...

  9. [ZJOI2015] 幻想乡战略游戏(树链剖分 + 线段树二分 + 带权重心)

    problem luogu-P3345 solution 这是一个带权重心的题,考察动态点分治.点分治?呵,不可能的,这辈子都不可能写点分治 我们重新考虑重心的性质:以这个点为根时,所有子树的大小不会 ...

最新文章

  1. 用Python构建网页抓取器
  2. 机器学习数据整合+pandas方法astype、merge、drop、to_numeric、concat等
  3. 中tile函数_HelpGirlFriend 系列 --- tensorflow 中的张量运算思想
  4. IIS不支持apk文件下载问题
  5. java exec 关闭_如何正确关闭java ExecutorService
  6. 最好的git教程_最好的Git教程
  7. 【Core Swagger】.NET Core中使用swagger
  8. 实用素材|UI设计师需要的输入框和表单
  9. Apache vs Nginx vs Tomcat vs JBoss vs Jetty
  10. 分析如何查找Linux死机的原因
  11. 密码战争,区块链技术之路
  12. latex设置页面大小边距行距等
  13. 聚类分析在用户行为中的实例_聚类分析案例
  14. 批量正则化Batch Normalization
  15. 耿丹CS16-2班第七次作业汇总
  16. Medium之1421.净现值查询
  17. js数字类型每隔三位加逗号(最简单方法)
  18. 科大讯飞杯”第18届上海大学程序设计联赛(H dfs暴力) L、动物森友会 (网络流题 延伸题 E、Sunscreen)
  19. AIR2 Betal版可以下载了flash player10.1可以下载了
  20. 新闻速递 | 恭喜肖晓容工程师获得Domo专业认证!

热门文章

  1. linux下安装nacos2.0
  2. JavaScript中大于符号与小于符号的问题
  3. html中的table跨行跨列用法
  4. 从改简历到面试需要知道的
  5. 数字货币支付模式通过某区块链支付平台在某资金支付项目中的业务处理流程分享
  6. 一般人自学软件测试,我劝你回头是岸~
  7. 动力节点王鹤SpringBoot3学习笔记——第二章 掌握SpringBoot基础篇
  8. 软件测试的4W1H(第1-2课时)
  9. php用户个人空间,毕业论文:多用户个人空间平台的设计和实现
  10. 一、前端入门学习-layout(布局)(3)