[笔记]点分治

ゝ一纸荒年。 2021-11-17 15:30 421阅读 0赞

基本思路:点分治,是一种针对可带权树上简单路径统计问题的算法。对于一个节点,只解决经过这棵子树的根节点的路径,对于子节点问题下推子树。

  1. //当初的主要问题是vis[]在干什么qwq,终于知道了
  2. #include<iostream>
  3. #include<cstdio>
  4. #include<algorithm>
  5. #define R register int
  6. using namespace std;
  7. #define ull unsigned long long
  8. #define ll long long
  9. #define pause (for(R i=1;i<=10000000000;++i))
  10. #define In freopen("NOIPAK++.in","r",stdin)
  11. #define Out freopen("out.out","w",stdout)
  12. namespace Fread {
  13. static char B[1<<15],*S=B,*D=B;
  14. #ifndef JACK
  15. #define getchar() (S==D&&(D=(S=B)+fread(B,1,1<<15,stdin),S==D)?EOF:*S++)
  16. #endif
  17. inline int g() {
  18. R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix;
  19. if(ch==EOF) return EOF; do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix;
  20. } inline bool isempty(const char& ch) {
  21. return (ch<=36||ch>=127);}
  22. inline void gs(char* s) {
  23. register char ch; while(isempty(ch=getchar()));
  24. do *s++=ch; while(!isempty(ch=getchar()));
  25. }
  26. } using Fread::g; using Fread::gs;
  27. namespace Jack {
  28. const int N=10010,Inf=0x3f3f3f3f;
  29. int n,m,cnt,sum,rt,tot;
  30. int vr[N<<1],nxt[N<<1],w[N<<1],fir[N],sz[N],mx[N],d[N],a[N],b[N],q[110];
  31. bool ans[110],vis[N];
  32. inline bool cmp(int a,int b) {
  33. return d[a]<d[b];}//按路径长度排序
  34. inline void add(int u,int v,int ww) {vr[++cnt]=v,nxt[cnt]=fir[u],w[cnt]=ww,fir[u]=cnt;}
  35. inline void getrt(int u,int fa) { sz[u]=1,mx[u]=0;//找根节点
  36. for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
  37. if(v==fa||vis[v]) continue;//若是father(vis避免扫回父亲),就continue
  38. getrt(v,u); sz[u]+=sz[v];//合并子树的size
  39. mx[u]=max(mx[u],sz[v]);//取max
  40. } mx[u]=max(mx[u],sum-sz[u]);
  41. if(!rt||mx[u]<mx[rt]) rt=u;//选根节点
  42. }
  43. inline void dfs(int u,int fa,int top) {
  44. a[++tot]=u;//将子树中的点添加到队列中
  45. b[u]=top;//记录所属次级子树(即本次分治节点的子树)的根节点
  46. for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
  47. if(v==fa||vis[v]) continue;
  48. d[v]=d[u]+w[i]; dfs(v,u,top);
  49. }
  50. }
  51. inline void calc(int u) {
  52. //计算经过u的路径条数
  53. tot=0; a[++tot]=u;//初始化队列
  54. d[u]=0; b[u]=u;
  55. for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
  56. if(vis[v]) continue;//不访问已经分治过的father
  57. d[v]=w[i]; dfs(v,u,v);
  58. } sort(a+1,a+tot+1,cmp);//按到当前根的距离排序
  59. for(R i=1;i<=m;++i) {
  60. if(ans[i]) continue;
  61. R l=1,r=tot; //双指针扫一遍
  62. while(l<r) {
  63. if(d[a[l]]+d[a[r]]>q[i]) --r;//过大则左移右指针
  64. else if(d[a[l]]+d[a[r]]<q[i]) ++l;//过小右移左指针
  65. else if(b[a[l]]==b[a[r]]) //同属于一棵子树
  66. if(d[a[r]]==d[a[r-1]]) --r;//右边权值相等左移右指针
  67. else ++l;
  68. else {ans[i]=true; break;}
  69. }
  70. }
  71. }
  72. inline void solve(int u) { vis[u]=true; //已经过,打标记
  73. calc(u);
  74. for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
  75. if(vis[v]) continue;//vis[u],表示已经分治过的父亲。
  76. sum=sz[v]; rt=0;
  77. getrt(v,0);
  78. solve(rt);//传入重心,solve
  79. }
  80. }
  81. void main() {
  82. n=g(),m=g(); for(R i=1,u,v,w;i<n;++i)
  83. u=g(),v=g(),w=g(),add(u,v,w),add(v,u,w);
  84. for(R i=1;i<=m;++i) q[i]=g();
  85. mx[rt]=sum=n;
  86. getrt(1,0);
  87. solve(rt);
  88. for(R i=1;i<=m;++i)
  89. ans[i]?printf("AYE\n"):printf("NAY\n");
  90. }
  91. }
  92. signed main() {
  93. Jack::main();
  94. }

转载于:https://www.cnblogs.com/Jackpei/p/11191034.html

发表评论

表情:
评论列表 (有 0 条评论,421人围观)

还没有评论,来说两句吧...

相关阅读

    相关 分治-模板

    可能一步步克服恐惧的过程就是成功,所以只管面对恐惧,不思考结果, 24分钟敲模板,这是点分治第一次敲的这么顺利,纪念一下。 洛谷:2634.求距离是三的倍数的点对数

    相关 分治初步

      点分治是一种常用于处理树上点对关系的分治算法。   一、算法介绍   提到点分治,我们先来看一道例题:[洛谷P3806 【模板】点分治1][P3806 _1]

    相关 分治学习笔记

    哗我看了一下好像没有很详细专门讲分治的blog?那就主要先学一下点分治吧,其他的……等我记得把C++一本通带到机房来再说吧先咕着啦 > 写在前面 > > 刷题进度 > >

    相关 分治

      [传送门][Link 1](洛谷) UPDATE:关于代码中时间复杂度的更正——在代码中每一次都要通过遍历一遍询问来得到答案,所以时间复杂度是O(NMlogN), !

    相关 [笔记]分治

    基本思路:点分治,是一种针对可带权树上简单路径统计问题的算法。对于一个节点,只解决经过这棵子树的根节点的路径,对于子节点问题下推子树。 //当初的主要问题是vis[]

    相关 分治学习笔记

    点分治 关于点分治,其实思想是非常好理解的,类比在数列上或是在平面上的分治算法(如归并排序,平面最近点对等),我们可以从字面上理解该算法: > 以一个点为界限,将一棵树