题目链接

1.对于简单的版本n<=500, ai<=50

直接暴力枚举两个点x,y,dfs求x与y的距离。

2.对于普通难度n<=10000,ai<=500

普通难度解法挺多

第一种,树形dp+LCA

比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2

然后找出所有互质的对数,然后对为1的数进行特殊处理。(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7)

对所有的非1质数对,采用离线LCA可以搞定。

对于1的特殊情况,只需要用两次dfs,就可以找出所有1到其它点的距离和与1之间的距离和。

第二种,树形dp+容斥

这种方法从边的角度,考虑每一条边会被计算多少次,这也是树上求距离的常用方法。

由于树边都是桥边,所有只要求出边两边联通块之间互质对数。最简单的想法即是枚举每一条边,然后再分别枚举两边区域,这样的复杂度是500*500*10000 很遗憾并没有这么简单。于是用容斥原理进行优化。在枚举某条边的一边的x(1<=x<=500)的时候,考虑右边为x质因子倍数的情况,也就是容斥了。 这样可以将复杂度变为10000*500*k*2^k( k<=4)

官方题解:

附上代码:

  1. //
  2. // main.cpp
  3. // 160701
  4. //
  5. // Created by New_Life on 16/7/1.
  6. // Copyright © 2016年 chenhuan001. All rights reserved.
  7. //
  8.  
  9. #include <iostream>
  10. #include <stdio.h>
  11. #include <string.h>
  12. #include <vector>
  13. #include <algorithm>
  14. using namespace std;
  15. #define N 10100
  16.  
  17. vector<int> save[];
  18. int g[N];
  19. struct node
  20. {
  21. int to,next;
  22. }edge[*N];
  23.  
  24. int cnt,pre[N];
  25. int dp[N][];
  26. int savecnt[N][];
  27. int cntall[];
  28. long long ans;
  29.  
  30. void add_edge(int u,int v)
  31. {
  32. edge[cnt].to = v;
  33. edge[cnt].next = pre[u];
  34. pre[u] = cnt++;
  35. }
  36.  
  37. void dfs(int s,int fa)
  38. {
  39. for(int p=pre[s];p!=-;p=edge[p].next)
  40. {
  41. int v = edge[p].to;
  42. if(v == fa) continue;
  43. dfs(v,s);
  44. for(int i=;i<=;i++)
  45. {
  46. dp[s][i] += dp[v][i];
  47. savecnt[s][i] += savecnt[v][i];
  48. }
  49. }
  50.  
  51. savecnt[s][ g[s] ]++;
  52.  
  53. for(int i=;i<(<<save[g[s]].size());i++)
  54. {
  55. int tmp = ;
  56. for(int j=;j<save[g[s]].size();j++)
  57. {
  58. if(((<<j)&i) != )
  59. {
  60. tmp *= save[g[s]][j];
  61. }
  62. }
  63. dp[s][tmp]++;
  64. }
  65.  
  66. //int last[505];
  67. int lastcnt[];
  68. for(int p=pre[s];p!=-;p=edge[p].next)
  69. {
  70. int v = edge[p].to;
  71. if(v == fa) continue;
  72. for(int i=;i<=;i++)
  73. {
  74. //last[i] = all[i]-dp[v][i];
  75. lastcnt[i] = cntall[i]-savecnt[v][i];
  76. }
  77. //对这条边进行处理
  78. for(int i=;i<=;i++)
  79. {
  80. if(lastcnt[i] == ) continue;
  81. for(int j=;j<(<<save[i].size());j++)
  82. {
  83. int tcnt=;
  84. int tnum = ;
  85. for(int k=;k<save[i].size();k++)
  86. {
  87. if( ((<<k)&j)!= )
  88. {
  89. tcnt++;
  90. tnum *= save[i][k];
  91. }
  92. }
  93. if(tcnt% == ) ans += lastcnt[i]*dp[v][tnum];
  94. else ans -= lastcnt[i]*dp[v][tnum];
  95. }
  96. }
  97. }
  98.  
  99. }
  100.  
  101. int main(int argc, const char * argv[]) {
  102. for(int i=;i<=;i++)
  103. {
  104. int ti = i;
  105. for(int j=;j*j<=ti;j++)
  106. {
  107. if(ti%j == )
  108. {
  109. save[i].push_back(j);
  110. while(ti%j==) ti/=j;
  111. }
  112. }
  113. if(ti != ) save[i].push_back(ti);
  114. }
  115. //for(int i=1;i<=500;i++) printf("%d\n",save[i].size());
  116. cnt = ;
  117. memset(pre,-,sizeof(pre));
  118. int n;
  119. scanf("%d",&n);
  120. for(int i=;i<=n;i++)
  121. {
  122. scanf("%d",g+i);//得把每一项都变成最简单
  123. int tg =;
  124. for(int j=;j<save[ g[i] ].size();j++)
  125. {
  126. tg *= save[ g[i] ][j];
  127. }
  128. g[i] = tg;
  129. }
  130. for(int i=;i<n;i++)
  131. {
  132. int a,b;
  133. scanf("%d%d",&a,&b);
  134. add_edge(a,b);
  135. add_edge(b,a);
  136. }
  137. ans = ;
  138. for(int ii=;ii<=n;ii++)
  139. {
  140. cntall[ g[ii] ]++;
  141. }
  142.  
  143. dfs(,-);
  144. cout<<ans<<endl;
  145. return ;
  146. }

3. 对于困难难度

这就需要用到虚树这种没听过的东西了,百度学习下,然后发现原理还是很简单的。应用情景,对于一颗树,挺大,但是需要操作的结点不多,这时候把需要操作的结点重新建一颗小的树(需要用的信息不能少)。

思路概述:(抄了下)

  1. 枚举 因数x,x是每种质因子至多有一个的数,记录一下x有几种质因子,方便之后容斥。
  2. 把所有x的倍数的权值的点找出来,预处理下可以做到找出来的点的dfs序是从小到大的,预处理也可以使得每次找x的倍数的权值的点不必线性扫一遍。
  3. 然后对这些点 O(n) 建虚树,具体操作是相邻两个点加进去 lca,用一个栈维护下父亲链即可。[bzoj3572]是一道典型的虚树的题目。
  4. 构建好树后在树上 dfs 两次可以求出所有x的倍数的权值的点对之间的距离和,就是第一遍dfs记录以节点u为根的子树中,有多少个x倍数的点(可能有一些是虚树添加进来的lca点),第二遍dfs其实是枚举每条边,计算(u,v)这条边的总价值,就是它出现的次数乘以它的权值;它出现的次数就是它子树中x倍数的点的个数,乘以不在它子树中x倍数的点的个数。
  5. 最后容斥下就可以求出答案。

由于所有步骤均是线性的,而所有虚树加起来的总点数也是线性乘上一个常数的,所以复杂度为 O(nK),K<=128。

对于复杂度分析,我抱有不同的看法,上述过程中建虚树是O(nlog(n))的,100000以内不重复质数最多是6个,所以最大复杂度为O(64*n*log(n))

  1. //
  2. // main.cpp
  3. // Xushu
  4. //
  5. // Created by New_Life on 16/7/1.
  6. // Copyright © 2016年 chenhuan001. All rights reserved.
  7. //
  8.  
  9. #include <iostream>
  10. #include <stdio.h>
  11. #include <string.h>
  12. #include <vector>
  13. #include <algorithm>
  14. using namespace std;
  15.  
  16. #define N 100100
  17. #define LN 20
  18.  
  19. struct node
  20. {
  21. int to,next;
  22. }edge[*N];
  23.  
  24. int cnt,pre[N];
  25.  
  26. void add_edge(int u,int v)
  27. {
  28. edge[cnt].to = v;
  29. edge[cnt].next = pre[u];
  30. pre[u] = cnt++;
  31. }
  32.  
  33. int deep[N];
  34. int g[N];//记录每个点的权值
  35. vector<int>saveall[N];//记录i所有的倍数
  36. int sign[N];
  37. int len[N];//每个点到根的距离
  38. int mark[N];//标示虚树上的点是否是无用点
  39.  
  40. struct Lca_Online
  41. {
  42. int _n;
  43.  
  44. int dp[N][LN];
  45.  
  46. void _dfs(int s,int fa,int dd)
  47. {
  48. int factor[];
  49. int fcnt=;
  50. int tmp = g[s];
  51. for(int i=;i*i<=tmp;i++)
  52. {
  53. if(tmp%i == )
  54. {
  55. factor[ fcnt++ ] = i;
  56. while(tmp%i == ) tmp/=i;
  57. }
  58. }
  59. if(tmp != ) factor[ fcnt++ ] = tmp;
  60.  
  61. for(int i=;i<(<<fcnt);i++)
  62. {
  63. tmp = ;
  64. int tsign = ;
  65. for(int j=;j<fcnt;j++)
  66. if( ((<<j)&i) != )
  67. {
  68. tmp *= factor[j];
  69. tsign *= -;
  70. }
  71. saveall[tmp].push_back(s);
  72. sign[tmp] = tsign;
  73.  
  74. }
  75.  
  76. deep[s] = dd;
  77. for(int p=pre[s];p!=-;p=edge[p].next)
  78. {
  79. int v = edge[p].to;
  80. if(v == fa) continue;
  81. _dfs(v,s,dd+);
  82. dp[v][] = s;
  83. }
  84. }
  85.  
  86. void _init()
  87. {
  88. for(int j=;(<<j)<=_n;j++)
  89. {
  90. for(int i=;i<=_n;i++)
  91. {
  92. if(dp[i][j-]!=-) dp[i][j] = dp[ dp[i][j-] ][j-];
  93. }
  94. }
  95. }
  96. void lca_init(int n)
  97. {
  98. _n = n;
  99. memset(dp,-,sizeof(dp));
  100. //_dfs(firstid,-1,0);
  101. _dfs(,-,);
  102. _init();
  103. }
  104.  
  105. int lca_query(int a,int b)
  106. {
  107. if(deep[a]>deep[b]) swap(a,b);
  108. //调整b到a的同一高度
  109. for(int i=LN-;deep[b]>deep[a];i--)
  110. if(deep[b]-(<<i) >= deep[a]) b = dp[b][i];
  111. if(a == b) return a;
  112. for(int i=LN-;i>=;i--)
  113. {
  114. if(dp[a][i]!=dp[b][i]) a = dp[a][i],b = dp[b][i];
  115. }
  116. return dp[a][];
  117. }
  118. }lca;
  119.  
  120. int stk[N],top;
  121. vector<int>tree[N];//存边
  122. vector<int>treew[N];//存权
  123.  
  124. void tree_add(int u,int v,int w)
  125. {
  126. tree[u].push_back(v);
  127. tree[v].push_back(u);
  128. treew[u].push_back(w);
  129. treew[v].push_back(w);
  130. }
  131.  
  132. long long down[N];
  133. long long ccnt[N];
  134. long long sum[N];
  135. int nn;
  136.  
  137. void dfs1(int s,int fa)
  138. {
  139. down[s] = ;
  140. ccnt[s] = ;
  141. for(int i=;i<tree[s].size();i++)
  142. {
  143. int to = tree[s][i];
  144. if(to == fa) continue;
  145. dfs1(to,s);
  146. down[s] += down[to] + ccnt[to]*treew[s][i];
  147. ccnt[s] += ccnt[to];
  148. }
  149. if(mark[s]==)
  150. ccnt[s]++;
  151. }
  152.  
  153. void dfs2(int s,int fa,long long num,long long tcnt)
  154. {
  155. sum[s] = down[s]+num+tcnt;
  156. for(int i=;i<tree[s].size();i++)
  157. {
  158. int to = tree[s][i];
  159. if(to == fa) continue;
  160. dfs2(to,s,sum[s]-down[to]-ccnt[to]*treew[s][i],(nn-ccnt[to])*treew[s][i]);
  161. }
  162. }
  163.  
  164. int main(int argc, const char * argv[]) {
  165. cnt = ;
  166. memset(pre,-,sizeof(pre));
  167. int n;
  168. scanf("%d",&n);
  169. for(int i=;i<=n;i++)
  170. {
  171. scanf("%d",g+i);
  172. }
  173. for(int i=;i<n;i++)
  174. {
  175. int a,b;
  176. scanf("%d%d",&a,&b);
  177. add_edge(a, b);
  178. add_edge(b, a);
  179. }
  180.  
  181. lca.lca_init(n);
  182. long long ans=;
  183.  
  184. for(int x=;x<=;x++)
  185. {
  186. if(saveall[x].size() == ) continue;
  187. //build virtual tree
  188. top = ;
  189.  
  190. stk[top++] = saveall[x][];
  191. tree[ saveall[x][] ].clear();
  192. treew[ saveall[x][] ].clear();
  193. mark[saveall[x][]]=;
  194. for(int i=;i<saveall[x].size();i++)
  195. {
  196. int v = saveall[x][i];
  197.  
  198. int plca = lca.lca_query(stk[top-], v);//最近公共祖先
  199. if(plca == stk[top-]) ;//不处理
  200. else
  201. {
  202.  
  203. int pos=top-;
  204. while(pos>= && deep[ stk[pos] ]>deep[plca])
  205. pos--;
  206. pos++;
  207. for(int j=pos;j<top-;j++)
  208. {
  209. tree_add(stk[j],stk[j+],deep[stk[j+]]-deep[stk[j]]);
  210. }
  211. int prepos = stk[pos];
  212. if(pos == )
  213. {
  214. tree[plca].clear(),treew[plca].clear(),stk[]=plca,top=;
  215. mark[plca] = ;
  216. }
  217. else if(stk[pos-] != plca)
  218. {
  219. tree[plca].clear(),treew[plca].clear(),stk[pos]=plca,top=pos+;
  220. mark[plca] = ;
  221. }
  222. else top = pos;
  223. tree_add(prepos,plca,deep[prepos]-deep[plca]);
  224.  
  225. }
  226. tree[v].clear();
  227. treew[v].clear();
  228. stk[top++] = v;
  229. mark[v] = ;
  230. }
  231. for(int i=;i<top-;i++)
  232. {
  233. tree_add(stk[i], stk[i+], deep[stk[i+]]-deep[stk[i]]);
  234. }
  235. //构建好了虚树,然后就是两次dfs
  236. nn = (int)saveall[x].size();
  237. dfs1(saveall[x][],-);
  238. dfs2(saveall[x][],-,,);
  239. long long tans=;
  240. for(int i=;i<saveall[x].size();i++)
  241. tans += sum[ saveall[x][i] ];
  242. tans /= ;
  243.  
  244. ans += sign[x]*tans;
  245. }
  246.  
  247. cout<<ans<<endl;//时间,内存。
  248.  
  249. return ;
  250. }

青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)的更多相关文章

  1. floyd算法 青云的机房组网方案(简单)

    青云的机房组网方案(简单) 青云现在要将 nn 个机房连成一个互相连通的网络.工程师小王设计出一个方案:通过在 nn 个机房之间铺设 n-1n−1 条双向的光纤,将所有的机房连接.可以假设数据在两个机 ...

  2. 2016 计蒜之道 初赛 第一场 D 青云的机房组网方案 (虚树)

    大意: 给定树, 点$i$的点权为$a_i$, 求$\sum\limits_{a_i \perp a_j}dis(i,j)$ 中等难度可以枚举每条边的贡献, 维护子树内每个数出现次数$a$, 转化为求 ...

  3. PC-网络教程之宽带小型组网方案

    由于某些家庭或小型局域网用户的各种需求和设备不同,所以继续写出几个组网方案让大家参考参考.如有错误之处,欢迎大家多多指点. 1,用网桥实现增加接入点(比如你有5台机子要上网,而你的小型路由只有4个接口 ...

  4. NB-IOT/LoRa/Zigbee无线组网方案对比

    物联网设备节点组网存在2种组网方式, 无线组网和有线组网. 无线组网我们常见到的有Zigbee,LoRa, NB-IOT等,其中Lora/NB-IOT属于LPWAN技术,LPWAN技术有覆盖广.连接多 ...

  5. 5G组网方案:NSA和SA

    目录 5G组网的8个选项 独立组网(SA) 选项1 选项2 选项5 选项6 总结 非独立组网(NSA) 选项3系列 选项3 选项3a 选项3x 选项7系列 选项4系列 选项8 演进路线 5G组网的8个 ...

  6. poj 2342 Anniversary party 简单树形dp

    Anniversary party Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 3862   Accepted: 2171 ...

  7. HDU 1796How many integers can you find(简单容斥定理)

    How many integers can you find Time Limit: 12000/5000 MS (Java/Others)    Memory Limit: 65536/32768 ...

  8. hdu4705 Y 简单树形DP 2013多校训练第十场 J题

    题意:求一棵树中不在一条链中的三个点的对数. 转化一下,用总对数减去在一条链上的三点对数即可. 考虑经过根节点,然后可能是不同的子树中各选一个:或者是子树中选一个,然后当前节点为根的子树以外的节点选一 ...

  9. 将简单的lambda表达式树转为对应的sqlwhere条件

    1.Lambda的介绍 园中已经有很多关于lambda的介绍了.简单来讲就是vs编译器给我带来的语法糖,本质来讲还是匿名函数.在开发中,lambda给我们带来了很多的简便.关于lambda的演变过程可 ...

随机推荐

  1. 字符编码的过滤器Filter(即输入的汉字,能在页面上正常显示,不会出现乱码)

    自定义抽象的 HttpFilter类, 实现自 Filter 接口 package com.lanqiao.javaweb; import java.io.IOException; import ja ...

  2. 如何利用java把文件中的Unicode字符转换为汉字

    有些文件中存在Unicode字符和非Unicode字符,如何利用java快速的把文件中的Unicode字符转换为汉字而不影响文件中的其他字符呢, 我们知道虽然java 在控制台会把Unicode字符直 ...

  3. Cube Stacking

    Cube Stacking Time Limit: 2000MS Memory Limit: 30000K Total Submissions: 21350 Accepted: 7470 Case T ...

  4. Poj(1466),最大独立集,匈牙利算法

    题目链接:http://poj.org/problem?id=1466 Girls and Boys Time Limit: 5000MS   Memory Limit: 10000K Total S ...

  5. C#中通过三边长判断三角形类型(三角形测试用例)

    对于<编程之美>P292上关于三角形测试用例的问题,题目是这样的: 输入三角形的三条边长,判断是否能构成一个三角形(不考虑退化三角形,即面积为零的三角形),是什么样的三角形(直角.锐角.钝 ...

  6. 2016CCPC东北地区大学生程序设计竞赛 1008 HDU5929

    链接http://acm.hdu.edu.cn/showproblem.php?pid=5929 题意:给你一种数据结构以及操作,和一种位运算,最后询问:从'栈'顶到低的运算顺序结果是多少 解法:根据 ...

  7. 织梦cms PHPcms 帝国cms比较

    现在建网站不需要请程序员从基础的程序开发做起了,有专业的建站工具,CMS是使用最广泛的建站工具.CMS是Content Management System 现在建网站不需要请程序员从基础的程序开发做起 ...

  8. Redis的WEB界面管理工具phpRedisAdmin

    下载地址:http://down.admin5.com/php/75024.html 官方网址:https://github.com/ErikDubbelboer/phpRedisAdmin

  9. linux ssh 使用深度解析(key登录详解)

    SSH全称Secure SHell,顾名思义就是非常安全的shell的意思,SSH协议是IETF(Internet Engineering Task Force)的Network Working Gr ...

  10. linux下的./本质

    不知道从什么时候对于./的感觉就是这是一条运行命令,因为你要运行某个文件的时候就用./ 但是这个显然是错误的./表述的是当前目录 .就是表示当前目录的.至于为什么运行当前目录下的 文件需要加上./原因 ...