【模版】【P3806】点分治
(7.17)早就想学点分治了……今天状态不太在线,眯一会写篇笔记来理理思路。
--------------------------------------------------------------------
(静态)点分治是一种利用无根树性质暴力分治的思想,可以在O(nlogn)的复杂度下统计可带权树上的路径信息。
像是这道例题,多组询问是否存在长度为k的路径,需要我们预处理出一个储存所有路径长度信息的桶。
点分治的做法,就是选定一个合适的根节点,把树上的所有路径分成不重不漏的两部分来统计:
1、经过根节点u的路径;
2、在u某个子树中的路径。
每次分治我们会统计出第一种路径的信息,然后递归进入u的每个子树,将第二种路径看作它的子树内的子问题来求解。
首先,我们要选定一个合适的根节点开始分治。最理想的根节点要满足它的每个子树大小都基本一样大;于是我们就想起了重心这个好东西。
无根树的重心u的性质:
1、最大子树的大小最小。
2、最大的子树大小小于等于树大小的一半。
如果每次选定该子树的重心为根来进行分治,我们就可以保证递归的进行不超过logn层。
- void find_rt(int u, int pre) {
- size[u] = 1;
- int Mx = 0;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v == pre || vis[v]) continue;
- find_rt(v, u);
- size[u] += size[v];
- Mx = max(Mx, size[v]);
- }
- Mx = max(Mx, Size - size[u]);
- if (Mx < Mn)
- root = u, Mn = Mx;
- }
--------------------------------------------------------------------
接下来是分治和统计的过程。O(nlogn)实际上是算法框架的复杂度,实际复杂度会随统计手段而改变。
针对这道题来说,我们通过一次暴力dfs统计出当前子树中每个点的路径信息(包括根自身,深度为0),然后继续很暴力地两两组合路径,然后就出问题了……
直接合并任意两条路径是行不通的,因为这两条路径可能来自u的同一个子节点v。此时我们得到的这条不合法路径的信息把u--->v的这条边统计了两次,所以我们要再遍历一遍它的每个子树,把这些不合法路径去掉。具体的操作可以看代码,用到了容斥原理。
- void dfs(int u, int pre, int depth) {
- chd[++tot] = depth; //记录每个子节点深度
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v == pre || vis[v])
- continue;
- dfs(v, u, depth + edge[i].w);
- }
- }
- void solve(int u, int extra, bool f) { //第三个参数表示加减
- tot = 0;
- dfs(u, 0, extra);
- if (f) {
- for (int i = 1; i <= tot; ++i)
- for (int j = i + 1; j <= tot; ++j)
- ++ans[chd[i] + chd[j]];
- } else {
- for (int i = 1; i <= tot; ++i)
- for (int j = i + 1; j <= tot; ++j)
- --ans[chd[i] + chd[j]];
- }
- }
- void divide(int u) { //分治过程
- vis[u] = true;
- solve(u, 0, 1);
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (vis[v]) continue;
- solve(v, edge[i].w, 0); //第二个参数为初始的深度,保证与以u为根算出的深度统一。
- Mn = inf, Size = size[v];
- find_rt(v, 0);
- divide(root);
- }
- }
大概不管是谁看到这里都想吐槽了:这个常数大到哪里去了?每回都要多跑一遍O(n)的搜索和O(n^2)的统计,虽然复杂度没有变,但是接受不能。
实际上该题分治的过程有第二种写法,但是我还没有掌握,所以今天就先更新到这里。
--------------------------------------------------------------
(7.19)肝出了点分治的第二种写法。
我们完全可以通过直接进行不重不漏的统计来避免容斥。处理u时,我们每次跑出点u的一个子树内的所有深度,把它们计入子树深度信息的同时,与之前得到的别的子树的信息组合,统计答案。注意这里要把点u本身计入这个child数组中,深度为0,这样就涵盖了路径结尾在u的情况。
代码:
- int chd[maxn], temp, tot;
- void dfs(int u, int pre, int d) {
- chd[++tot] = d;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v == pre || vis[v])
- continue;
- dfs(v, u, d + edge[i].w);
- }
- }
- void solve(int u, int extra) {
- temp = tot;
- dfs(u, 0, extra);
- for (register int i = temp + 1; i <= tot; ++i)
- for (register int j = 1; j <= temp; ++j)
- ++ans[chd[i] + chd[j]];
- }
- void divide(int u) {
- vis[u] = true;
- // memset(chd, 0, sizeof(chd));//直接覆盖原数组信息即可,不用拷贝也不用清空
- chd[1] = 0;
- tot = 1; //算上自己
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (vis[v]) continue;
- solve(v, edge[i].w);
- }
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (vis[v]) continue;
- Mn = inf, Size = size[v];
- find_rt(v, 0);
- divide(root);
- }
- }
这段代码的实测效率比上一种写法快了一倍(开O2快10倍@w@)。下面放上完整的代码:
- #include <iostream>
- #include <cstring>
- #include <cstdio>
- #define maxn 10010
- const int inf(0x3fffffff);
- using namespace std;
- template <typename T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- return;
- }
- int n, m;
- int head[maxn], top;
- struct E {
- int to, nxt, w;
- } edge[maxn << 1];
- inline void insert(int u, int v, int w) {
- edge[++top] = (E) {v, head[u], w};
- head[u] = top;
- }
- int Mn, root, Size, size[maxn], ans[10000010];
- bool vis[maxn];
- void find_rt(int u, int pre) {
- size[u] = 1;
- int Mxson = 0;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (vis[v] || v == pre)
- continue;
- find_rt(v, u);
- size[u] += size[v];
- if (size[v] > Mxson)
- Mxson = size[v];
- }
- Mxson = max(Mxson, Size - size[u]);
- if (Mxson < Mn)
- root = u, Mn = Mxson;
- }
- int chd[maxn], temp, tot;
- void dfs(int u, int pre, int d) {
- chd[++tot] = d;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v == pre || vis[v])
- continue;
- dfs(v, u, d + edge[i].w);
- }
- }
- void solve(int u, int extra) {
- temp = tot;
- dfs(u, 0, extra);
- for (register int i = temp + 1; i <= tot; ++i)
- for (register int j = 1; j <= temp; ++j)
- ++ans[chd[i] + chd[j]];
- }
- void divide(int u) {
- vis[u] = true;
- // memset(chd, 0, sizeof(chd));//直接覆盖原数组信息即可,不用拷贝也不用清空
- chd[1] = 0;
- tot = 1; //算上自己
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (vis[v]) continue;
- solve(v, edge[i].w);
- }
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (vis[v]) continue;
- Mn = inf, Size = size[v];
- find_rt(v, 0);
- divide(root);
- }
- }
- int main() {
- // freopen("testdata.in.txt", "r", stdin);
- // freopen("testdata.out", "w", stdout);
- read(n), read(m);
- int u, v, w, k;
- for (register int i = 1; i < n; ++i) {
- read(u), read(v), read(w);
- insert(u, v, w), insert(v, u, w);
- }
- Size = n, Mn = inf;
- find_rt(1, 0);
- divide(root);
- for (register int i = 1; i <= m; ++i) {
- read(k);
- puts(ans[k] ? "AYE" : "NAY");
- }
- return 0;
- }
由于通过枚举每一条可行路径来n^2进行统计,这种写法有很大的局限性。例如【P4178】Tree 这道题,直接枚举统计会爆炸,需要排序和双指针扫描的技巧来成段统计可行路径。在大多数情况下还是需要使用容斥去重的方法进行点分治。
【模版】【P3806】点分治的更多相关文章
- 【模板】P3806点分治1
[模板]P3806 [模板]点分治1 很好的一道模板题,很无脑经典. 讲讲淀粉质吧,很营养,实际上,点分治是树上的分治算法.根据树的特性,树上两点的路径只有一下两种情况: 路径经过根\((*)\) 路 ...
- 洛谷 P3806 点分治模板
题目:https://www.luogu.org/problemnew/show/P3806 就是点分治~ 每次暴力枚举询问即可,复杂度是 nmlogn: 注意 tmp[0]=1 ! 代码如下: #i ...
- 洛谷P3806 点分治
点分治 第一次写点分治..感觉是一个神奇而又暴力的东西orz 点分治大概就是用来处理树上链的信息,把路径分成过点x和不过点x的两种,不过点x的路径可以变成过点x的子树中一点的路径,递归处理 #incl ...
- 洛谷P3806 点分治1 & POJ1741 Tree & CF161D Distance in Tree
正解:点分治 解题报告: 传送门1! 传送门2! 传送门3! 点分治板子有点多,,,分开写题解的话就显得很空旷,不写又不太好毕竟初学还是要多写下题解便于理解 于是灵巧发挥压行选手习惯,开始压题解(bu ...
- 【Luogu】P3806点分治模板(点分治)
题目链接 wc听不懂lca讲的高等数学专场(一个字都听不懂),然后就自学了点分治. 点分治就是我先处理完跟根有关的东西,然后把根标记掉,把原树拆成若干个联通块,然后分别对每个联通块(每个小树)搞一模一 ...
- Luogu P3806 点分治模板1
题意: 给定一棵有n个点的树询问树上距离为k的点对是否存在. 分析: 这个题的询问和点数都不多(但是显然暴力是不太好过的,即使有人暴力过了) 这题应该怎么用点分治呢.显然,一个模板题,我们直接用套路, ...
- 模板·点分治(luogu P3806)
[模板]洛谷·点分治 1.求树的重心 树的重心:若A点的子树中最大的子树的size[] 最小时,A为该树的中心 步骤: 所需变量:siz[x] 表示 x 的子树大小(含自己),msz[x] 表示 其子 ...
- [luogu P3806] 【模板】点分治1
[luogu P3806] [模板]点分治1 题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入输出格式 输入格式: n,m 接下来n-1条 ...
- 洛谷 P3806 【模板】点分治1
P3806 [模板]点分治1 题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入输出格式 输入格式: n,m 接下来n-1条边a,b,c描述 ...
随机推荐
- 国云数据:中国版的Snowflake,国内数据中台领导者
[股神巴菲特加持,今年最受关注美股IPO ] 这段时间, 由股神巴菲特54年来首次打新的美股IPO公司Snowflake迅速得到业界重点关注.Snowflake已于2020年9月16日正式上市,发行价 ...
- vue3.0-如何切换路由-路由模式ts
如何更换路由模式 vue3版本如何切换路由模式?(注:更改后要重启下项目才能看到效果.) 博主,我搜了大半天的,你是怎么找到的? 如下图可看到
- 学了Java才搞懂JMeter测试计划
本文的内容是对这个界面的东西进行讲解: Name 名字,测试计划总得有个名字. Comments 注释,除了名字,还得让人知道它是干嘛的. User Defined Variables 测试计划是根节 ...
- jquery播放图片
* { margin:0; padding:0; word-break:break-all; } body { background:#FFF; color:#333; font:12px/1.5em ...
- python爬虫05正则表达式
字符 描述 \ 将下一个字符标记为一个特殊字符(File Format Escape,清单见本表).或一个原义字符(Identity Escape,有^$()*+?.[\{|共计12个).或一个向后引 ...
- Linux C 获取本机所有网卡的 IP,Mask
0 运行环境 本机系统:Windows 10 虚拟机软件:Oracle VM VirtualBox 6 虚拟机系统:Ubuntu 18 1 代码 #include <sys/ioctl.h> ...
- C++ 设计模式 3:结构型模式
0 结构型模式 让类和类进行组合,获得更大的结构,获得新功能的方式. 1 代理模式 Proxy 模式又被叫做代理模式,是结构型的设计模式之一,它可以 为其他对象提供一种代理以控制对这个对象的访问. 所 ...
- C++ 基础 6:模板
1 函数模板 泛型(Generic Programming),即是指具有在多种数据类型上皆可操作的含意. 泛型编程 的代表作品 STL 是一种高效.泛型.可交互操作的软件组件. 泛型编程最初诞生于 C ...
- 《我想进大厂》之Java基础夺命连环16问
说好了面试系列已经完结了,结果发现还是真香,嗯,以为我发现我的Java基础都没写,所以这个就算作续集了,续集第一篇请各位收好. 说说进程和线程的区别? 进程是程序的一次执行,是系统进行资源分配和调度的 ...
- Spark Standalone模式 高可用部署
本文使用Spark的版本为:spark-2.4.0-bin-hadoop2.7.tgz. spark的集群采用3台机器进行搭建,机器分别是server01,server02,server03. 其 ...