【前言】

  最近一段时间变成了通过题目学习算法,似乎整个人都乱套了(反思ing)

  不过还好,现在又调整为了学算法后做题。(唉,最近一段时间有点急躁,要记住万事不能速成啊)

【正题】点分治

  一句话:点分治主要用于树上路径点权统计问题。

一、【具体流程】

1,选取一个点,将无根树变成有根树
 为了使每次的处理最优,我们通常要选取树的重心。
 何为“重心”,就是要保证与此点连接的子树的节点数最大值最小,可以防止被卡。
 重心求法:
  1。dfs一次,算出以每个点为根的子树大小。
  2。记录以每个节点为根的最大子树大小
  3。判断:如果以当前节点为根更优,就更新当前根。

void getroot(int v,int fa)
{son[v] = 1; f[v] = 0;//f记录以v为根的最大子树的大小 for(int i = head[v];i;i=e[i].next)if(e[i].to != fa && !vis[e[i].to]) {getroot(e[i].to,v);//递归更新 son[v] += son[e[i].to];f[v] = max(f[v],son[e[i].to]);//比较每个子树 }f[v] = max(f[v],sum-son[v]);//别忘了以v父节点为根的子树 if(f[v] < f[root]) root = v;//更新当前根
}

2、处理连通块中通过根节点的路径。
  (注意,是通过根节点的路径,所以后面要去掉同一子树内部的路径,即去重)
3、标记根节点(相当于处理后,将根节点从子树中删除)。
4、递归处理当前点为根的每棵子树。

int solve(int v)
{vis[v] = 1;//标记 for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to]) {root = 0;sum = son[e[i].to];getroot(e[i].to,v);solve(root);//递归处理下一个连通块 }
}
int main()
{sum = f[0] = n;//初始化 root = 0;getroot(1,0);//找重心 solve(root);//点分治
}

【注释】:作者是用 son[] 来表示节点x为根的子树大小,可能他人更多地是用size[]来表示,二者同意。

二、【POJ 1741 & BZOJ 1468 & BZOJ 3365】

给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K。
【题解】:
  我们找到树的重心,然后dfs,求出每个点到root的距离deep,然后对deep排序,扫描哪些点对是符合的。
  但是,点分治要求处理的路径是经过root,所以如果一条路径是在同一个子树之内的就不符合要求,所以还要对子树dfs一下,然后去重。
  接下来处理好root后,就可以处理其他连通块了,即递归其子树。
【代码】:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;#define N 10010
#define inf 1e9+10
struct node{int to,c,next;}g[N*2];
int head[N],m;
int son[N],f[N];
bool vis[N];
int d[N],deep[N];
int n,sum,root,k,ans;void add_edge(int from,int to,int cost)
{g[++m].next = head[from];head[from] = m;g[m].to = to; g[m].c = cost;
}void getroot(int v,int fa)
{son[v] = 1; f[v] = 0;for(int i = head[v];i;i=g[i].next)if(g[i].to != fa && !vis[g[i].to]){getroot(g[i].to,v);son[v] += son[g[i].to];f[v] = max(f[v],son[g[i].to]);}f[v] = max(f[v],sum - son[v]);if(f[v] < f[root]) root = v;
}void getdeep(int v,int fa)
{deep[++deep[0]] = d[v];for(int i = head[v];i;i=g[i].next)if(g[i].to != fa && !vis[g[i].to]){d[g[i].to] = d[v] + g[i].c;getdeep(g[i].to,v);}
}int cal(int v,int cost)
{d[v] = cost; deep[0] = 0;getdeep(v,0);sort(deep+1,deep+deep[0]+1);int l = 1,r = deep[0],sum = 0;while(l < r) {if(deep[l]+deep[r] <= k) {sum += r-l;l++;} else r--;}return sum;
}void solve(int v)
{ans += cal(v,0);vis[v] = 1;for(int i = head[v];i;i=g[i].next)if(!vis[g[i].to]){ans -= cal(g[i].to,g[i].c);sum = son[g[i].to];root = 0;getroot(g[i].to,0);solve(root);}
}int main()
{int u,v,w;while(scanf("%d%d",&n,&k) && n && k){ans = root = m = 0;memset(vis,0,sizeof(vis));memset(head,0,sizeof(head));for(int i = 1;i < n;i++){scanf("%d%d%d",&u,&v,&w);add_edge(u,v,w);add_edge(v,u,w);}f[0] = inf;sum = n;getroot(1,0);solve(root);printf("%d\n",ans);}return 0;
}

【补】:若是距离等于k,cal可以改成:

int cal(int v,int cost)
{d[v] = cost; deep[0] = 0;getdeep(v,0);sort(deep+1,deep+deep[0]+1);int r = deep[0],res = 0;for(int l = 1;l < r;l++)while(deep[l]+deep[r] >= k) {if(deep[l] + deep[r] == k) res++;r--;}return res;
}

三、【BZOJ 2152】

由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。
【题解】:
  感觉这道更好处理,不用快排,也不用去重。我们对于当前的树,直接找到重心V,然后从V出发,搜索与V相邻的点,计算边长的余数分别是是0,1,2的情况数,用t[0],t[1],t[2]分别表示。
  显然答案就是 t[1]*t[2]*2+t[0]*t[0]。
【代码】:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;#define N 20010
struct node{int to,c,next;}e[N*2];
int head[N],m;
int ans,root,t[4],d[N],son[N],f[N],sum;
bool vis[N];void add_edge(int from,int to,int cost)
{e[++m].next = head[from];head[from] = m;e[m].to = to; e[m].c = cost;
}void getroot(int v,int fa)
{son[v] = 1;f[v] = 0;for(int i = head[v];i;i = e[i].next)if(!vis[e[i].to] && e[i].to != fa){getroot(e[i].to,v);son[v] += son[e[i].to];f[v] = max(f[v],son[e[i].to]);}f[v] = max(f[v],sum-son[v]);if(f[v] < f[root]) root = v;
}void getdeep(int v,int fa)
{t[d[v]]++;for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to] && e[i].to != fa){d[e[i].to] = (d[v] + e[i].c)%3;getdeep(e[i].to,v);}
}int cal(int v,int w)
{t[0] = t[1] = t[2] = 0;d[v] = w;getdeep(v,0);return t[1]*t[2]*2+t[0]*t[0];
}void solve(int v)
{ans += cal(v,0); vis[v] = 1;for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to]){ans -= cal(e[i].to,e[i].c);root = 0; sum = son[e[i].to];getroot(e[i].to,0);solve(root);}
}inline int gcd(int a,int b){return b == 0 ? a : gcd(b,a%b);}
int main()
{int n,u,v,w;scanf("%d",&n);for(int i = 1;i < n;i++){scanf("%d%d%d",&u,&v,&w);w %= 3;add_edge(u,v,w); add_edge(v,u,w);}sum = n;f[0] = n;root = ans = 0;getroot(1,0);solve(root);int x = gcd(ans,n*n);printf("%d/%d\n",ans/x,n*n/x);return 0;
}

先到这里,我下去逛逛。未完待续……

好了,我又回来了

四、【BZOJ 2599】

给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000
【题解】:
参考黄学长的题解啊。
  开一个100W的数组t,t[i]表示权值为i的路径最少边数
  找到重心分成若干子树后, 得出一棵子树的所有点到根的权值和x,到根a条边,用t[k-x]+a更新答案,全部查询完后,再用所有a更新t[x],这样可以保证不出现点分治中的不合法情况。
  把一棵树的所有子树搞完后再遍历所有子树恢复T数组,如果用memset应该会比较慢
  
看的稀里糊涂的,但还是好像懂了一点啊。
  d数组 表示已经有几条边
  dis数组 表示子树中的点到根的距离
  add函数用于更新和初始化(好像有这个功能吧)
【代码】:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;#define N 200010
#define inf 1000000000
struct node{int to,c,next;}e[N*2];
int head[N],m,k;
bool vis[N];
int t[1000010];
int sum,f[N],dis[N],d[N],son[N],root,ans;void add_edge(int from,int to,int cost)
{e[++m].next = head[from];head[from] = m;e[m].to = to;e[m].c = cost;
}void getroot(int v,int fa)
{son[v] = 1;f[v] = 0;for(int i = head[v];i;i=e[i].next)if(e[i].to != fa && !vis[e[i].to]) {getroot(e[i].to,v);son[v] += son[e[i].to];f[v] = max(f[v],son[e[i].to]);}f[v] = max(f[v],sum-son[v]);if(f[v] < f[root]) root = v;
}void cal(int v,int fa)
{if(dis[v] <= k) ans = min(ans,d[v]+t[k-dis[v]]);for(int i = head[v];i;i=e[i].next)if(e[i].to != fa && !vis[e[i].to]) {d[e[i].to] = d[v] + 1;dis[e[i].to] = dis[v] + e[i].c;cal(e[i].to,v);}
}void add(int v,int fa,bool flag)
{if(dis[v] <= k) {if(flag) t[dis[v]] = min(t[dis[v]],d[v]);else t[dis[v]] = inf;}for(int i = head[v];i;i=e[i].next)if(e[i].to != fa && !vis[e[i].to])add(e[i].to,v,flag);
}void solve(int v)
{vis[v] = 1;t[0] = 0;for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to]) {d[e[i].to] = 1;dis[e[i].to] = e[i].c;cal(e[i].to,0);add(e[i].to,0,1);}for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to]) add(e[i].to,0,0);for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to]) {root = 0;sum = son[e[i].to];getroot(e[i].to,0);solve(root);}
}int main()
{int n,u,v,w;;scanf("%d%d",&n,&k);for(int i = 1;i <= k;i++) t[i] = n;for(int i = 1;i < n;i++) {scanf("%d%d%d",&u,&v,&w);u++; v++;add_edge(u,v,w); add_edge(v,u,w);}ans = sum = f[0] = n;root = 0;getroot(1,0);solve(root);if(ans != n) printf("%d\n",ans);else puts("-1");return 0;
}

啊!
今天对点分治的学习差不多就到这里了。
笔记结束,开始刷水题玩喽。

补:

五、【BZOJ 1316】

一棵n个点的带权有根树,有p个询问,每次询问树中是否存在一条长度为Len的路径,如果是,输出Yes否输出No.
【题解】:
  运用点分治统计点到重心的距离,再两次二分查找距离,判断有多少条路径长度为k(这样为了方便去重)
  听说点分治的常数比较大,所以将所有询问在一次点分治中一起做。
【代码】:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;#define N 10010
struct node{int to,w,next;}e[N*2];
int head[N],m,p;
int q[110],f[N],son[N],sum,root,d[N],deep[N];
bool vis[N];void add_edge(int from,int to,int cost)
{e[++m].next = head[from];head[from] =m;e[m].w = cost; e[m].to = to;
}void getroot(int v,int fa)
{son[v] = 1;f[v] = 0;for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to] && e[i].to != fa) {getroot(e[i].to,v);son[v] += son[e[i].to];f[v] = max(f[v],son[e[i].to]);}f[v] = max(f[v],sum-son[v]);if(f[v] < f[root]) root = v;
}void getdeep(int v,int fa)
{deep[++deep[0]] = d[v];for(int i = head[v];i;i=e[i].next)if(e[i].to != fa && !vis[e[i].to]) {d[e[i].to] = d[v] + e[i].w;getdeep(e[i].to,v);}
}int findl(int L,int R,int k)
{int ans = 0;while(L <= R){int mid = (L+R)>>1;if(deep[mid] == k){ans = mid;R = mid-1;}else if(deep[mid] < k) L = mid + 1;else R = mid - 1;}return ans;
}int findr(int L,int R,int k)
{int ans = -1;while(L <= R) {int mid = (L+R)>>1;if(deep[mid] == k){ans = mid;L = mid+1;}else if(deep[mid] < k) L = mid+1;else R = mid - 1;}return ans;
}int cal(int v,int now,int k)
{d[v] = now; deep[0] = 0;getdeep(v,0);sort(deep+1,deep+deep[0]+1);int t = 0;for(int i = 1;i <= deep[0];i++) {if(deep[i] + deep[i] > k) break;int l = findl(i,deep[0],k-deep[i]);int r = findr(i,deep[0],k-deep[i]);t += r-l+1;}return t;
}int ans[110];
void solve(int v)
{for(int i = 1;i <= p;i++) ans[i] += cal(v,0,q[i]);vis[v] = 1;for(int i = head[v];i;i=e[i].next)if(!vis[e[i].to]) {for(int j = 1;j <= p;j++)ans[j] -= cal(e[i].to,e[i].w,q[j]);sum = son[e[i].to];root = 0;getroot(e[i].to,0);solve(root);}
}int main()
{int n,u,v,w;scanf("%d%d",&n,&p);m = 0;for(int i = 1;i < n;i++){scanf("%d%d%d",&u,&v,&w);add_edge(u,v,w); add_edge(v,u,w);}for(int i = 1;i <= p;i++) scanf("%d",&q[i]);sum = f[0] = n;root = 0;getroot(1,0);solve(root);for(int i = 1;i <= p;i++)if(ans[i]) puts("Yes"); else puts("No");return 0;
}

吾 点分治 之道路大概结束于此。


PS:4月3日,第四次更新。

【点分治】的学习笔记和众多例题相关推荐

  1. 【教程】简易CDQ分治教程学习笔记

    前言 辣鸡蒟蒻__stdcall终于会CDQ分治啦!       CDQ分治是我们处理各类问题的重要武器.它的优势在于可以顶替复杂的高级数据结构,而且常数比较小:缺点在于必须离线操作. CDQ分治的基 ...

  2. Big-Small (根号分治) 学习笔记

    Big−SmallBig-SmallBig−Small 算法一般对度数进行分治,其时间复杂度可以规约为sqrt(m)sqrt(m)sqrt(m)级别 经典例题:P1989三元环计数问题 //#defi ...

  3. 感知机算法学习笔记(带例题及代码)

    感知机 感知机是二分类的线性分类模型,其输入为实例的特征向量,输出实例为类别,取+1和-1二值,属于判别模型.感知机学习旨在求出能够将训练数据集进行正确的分类的分离超平面的.为此,导入基于误分类的损失 ...

  4. [偏序关系与CDQ分治]【学习笔记】

    组合数学真是太棒了 $CDQ$真是太棒了(雾 参考资料: 1.<组合数学> 2.论文 课件 很容易查到 3.sro __stdcall 偏序关系 关系: 集合$X$上的关系是$X$与$X$ ...

  5. 多维前缀和 学习笔记 模板及例题

    一维前缀和大家都会,二维就更好理解了. 一维初始化: for(i=1;i<=n;i++){q[i]=(q[i-1]+a[i]);} 二维初始化其实花个图就可以了,这里直接上模板: q[1][1] ...

  6. [多项式算法](Part 4)FWT 快速沃尔什变换 学习笔记

    其他多项式算法传送门: [多项式算法](Part 1)FFT 快速傅里叶变换 学习笔记 [多项式算法](Part 2)NTT 快速数论变换 学习笔记 [多项式算法](Part 3)MTT 任意模数FF ...

  7. [学习笔记]CDQ分治

    分治,考虑前一半对后一半的影响. (和一般分治不太相同的思想是,一般分治不分谁对谁的影响,跨mid的都要统计.(全局变量统计) 而CDQ貌似要落脚到前一半对后一半的影响上,也就是贡献在后一半统计,由前 ...

  8. 算法学习 (门徒计划)4-1 单调队列及经典问题及经典例题 学习笔记

    算法学习 (门徒计划)4-1 单调队列及经典问题及经典例题 学习笔记 前言 单调队列 场景举例(RMQ) 应用-维护区间最值的方式 数据结构-自行设计单调队列 代码实现(java) 例题分析(略) 总 ...

  9. 【C++】【学习笔记】【递归与回溯问题详解与例题】排列问题;组合问题;二维平面回溯;flood fill问题;搜索问题(八皇后);

    目录 七.递归和回溯 1.回溯 2.回溯应用 - 排列问题 2.回溯应用 - 组合问题 3.回溯应用 - 二维平面 4.回溯应用 - floodfill算法 问题 4.回溯应用 - 搜索问题 - 八皇 ...

最新文章

  1. python基础:python扩展包的安装方式
  2. FastCGI与php-fpm
  3. Spring Boot 关于 @EnableConfigurationProperties 注解 —— 使用 @ConfigurationProperties 注解的类生效。
  4. 【C 语言】数组 ( 数组指针 | 数组指针定义 | 使用 数组类型* 定义数组指针 )
  5. C语言九十三之输入一个字符x,找到输入的那句话(字符串)里面一样字母的位置。
  6. tcp的3次握手4次挥手
  7. Bootstrap系列 -- 36. 向上弹起的下拉菜单
  8. theme为dialog的Activity如何充满全屏
  9. Vmware 安装centos7与网络配置
  10. Unity3D基础23:TrailRenderer特效
  11. 《WINDOWS游戏编程之从零开始》第四章学习笔记
  12. 小学听力测试英语软件,小学英语听力测试
  13. RabbitMQ-operation queue.declare caused a channel exception precondition_failed错误
  14. 120. Triangle(三角矩阵)
  15. python显示血量条,利用Python绘制血药浓度-时间曲线——口服吸收一室模型
  16. 软件测试基础知识 - 集成测试和系统测试的区别,以及它们的应用场景
  17. uefi +gpt 系统安装 和 传统legacy + mbr 的区别
  18. 5.MyBatis源码解析-MyBatis面试题--阿呆中二
  19. ODBC和ADO区别
  20. LTE下行物理层传输机制(4)-CCE

热门文章

  1. awk NR详解!awk 的内置变量 NF、NR、FNR、FS、OFS、RS、ORS
  2. JAVA判断两个数是否亲和数_亲和数
  3. mysql三叶草,温州日报瓯网 - 面对温州话,你被困住了吗?
  4. 袋鼠云数据湖平台「DataLake」,存储全量数据,打造数字底座
  5. matlab 双层规划求解,双层规划模型的遗传算法求解的Matlab源码
  6. 写作技巧~100段作文排比句(41-60段),考试一定用得上,赶紧收藏!
  7. Springcloud 介绍 和 Eureka的使用
  8. 道德经 道可道,非常道。
  9. 【总结】1361- package.json 与 package-lock.json 的关系
  10. 康考迪亚大学应用计算机科学,2020年康考迪亚大学IT专业会学什么内容