一道很好的圆方树入门题

感谢PinkRabbit巨佬的博客,讲的太好啦

首先是构建圆方树的代码,也比较好想好记

  1. void tarjan(int u) {
  2. dfn[u] = low[u] = ++dfn_clk; //初始化dfn和low数组
  3. stk[++tp] = u; //把u加入栈中
  4. for(int i = head[u]; i; i = e[i].next) {
  5. int v = e[i].to;
  6. if(!dfn[v]) { //v还未访问
  7. tarjan(v); //先访问
  8. low[u] = min(low[u], low[v]); //然后更新u的信息
  9. if(low[v] == dfn[u]) { //找到一个以u为顶点的点双
  10. ++tot; //新建一个方点
  11. for(int x = 0; x != v; --tp) { //把栈中在v及其之前的点都向方点连边并弹出
  12. x = stk[tp];
  13. G[tot].push_back(x);
  14. G[x].push_back(tot);
  15. }
  16. G[tot].push_back(u); //注意不能把u弹出
  17. G[u].push_back(tot); //因为u可能在多个点双中
  18. }
  19. }
  20. else low[u] = min(low[u], dfn[v]);
  21. }
  22. }

注释写的还算详细\(QWQ\)

考虑这一题怎么做

题目大意

给你一张无向图,让你求这样的有序三元组\(<s,c,f>\)的个数,使得存在一条简单路径依次经过\(s,c,f\)

Solution

首先我们把圆方树建出来

考虑如下性质,对于在同一个点双中的两点\(s,t\),还有一个给定的也在这个点双中的点\(c\),一定存在一条简单路径依次经过\(s,c,t\),貌似挺显然的

在这题中,假设钦定了路径的两个端点\(s,t\),由上面的性质,那么能作为中间点的点集就是在圆方树上\(s\)到\(t\)的路径所经过的方点所代表的点双的并集(不包括\(s,t\))。这句话是本题的突破点,虽然有点拗口

然后是一个很套路的处理,把方点的点权设为点双的大小,圆点的点权设为\(-1\),这样的话上面要求的值就转化为\(s\)到\(t\)路径上的点权之和了

直接枚举\(s\)和\(t\)显然不行,考虑枚举每个点对答案的贡献,即

\[w[u]=val[u]*经过u的路径条数
\]

然后用树形\(dp\)就可以\(O(n)\)的统计答案了

另外,注意图不一定联通,所以需要单独统计每个联通块中的答案

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define N 500000
  4. #define M 200000
  5. int n, m, tot;
  6. int head[N+5], eid;
  7. int dfn[N+5], low[N+5], dfn_clk;
  8. int stk[N+5], tp, val[N+M+5], vis[N+M+5], cnt[N+M+5], sz[N+M+5], S;
  9. long long ans;
  10. vector<int> G[N+5];
  11. struct Edge {
  12. int next, to;
  13. }e[2*M+5];
  14. void addEdge(int u, int v) {
  15. e[++eid].next = head[u];
  16. e[eid].to = v;
  17. head[u] = eid;
  18. }
  19. void tarjan(int u) {
  20. dfn[u] = low[u] = ++dfn_clk; //初始化dfn和low数组
  21. stk[++tp] = u; //把u加入栈中
  22. for(int i = head[u]; i; i = e[i].next) {
  23. int v = e[i].to;
  24. if(!dfn[v]) { //v还未访问
  25. tarjan(v); //先访问
  26. low[u] = min(low[u], low[v]); //然后更新u的信息
  27. if(low[v] == dfn[u]) { //找到一个以u为顶点的点双
  28. ++tot; //新建一个方点
  29. for(int x = 0; x != v; --tp) { //把栈中在u之前的点都向方点连边并弹出
  30. x = stk[tp];
  31. G[tot].push_back(x);
  32. G[x].push_back(tot);
  33. }
  34. G[tot].push_back(u); //注意不能把u弹出
  35. G[u].push_back(tot); //因为u可能在多个点双中
  36. }
  37. }
  38. else low[u] = min(low[u], dfn[v]);
  39. }
  40. }
  41. void getcnt(int u, int fa) {
  42. if(u <= n) cnt[u] = 1;
  43. for(int i = 0; i < G[u].size(); ++i) {
  44. int v = G[u][i];
  45. if(v == fa) continue;
  46. getcnt(v, u);
  47. cnt[u] += cnt[v];
  48. }
  49. }
  50. void dp(int u, int fa) {
  51. vis[u] = 1;
  52. if(u <= n) sz[u] = 1;
  53. long long sum = 1LL*cnt[u]*(S-cnt[u]);
  54. for(int i = 0; i < G[u].size(); ++i) {
  55. int v = G[u][i];
  56. if(v == fa) continue;
  57. dp(v, u);
  58. sum += 1LL*sz[u]*sz[v];
  59. sz[u] += sz[v];
  60. }
  61. ans += 1LL*val[u]*sum;
  62. }
  63. int main() {
  64. scanf("%d%d", &n, &m);
  65. tot = n;
  66. for(int i = 1, x, y; i <= m; ++i) {
  67. scanf("%d%d", &x, &y);
  68. addEdge(x, y), addEdge(y, x);
  69. }
  70. for(int i = 1; i <= n; ++i)
  71. if(!dfn[i]) tarjan(i);
  72. for(int i = 1; i <= n; ++i) val[i] = -1;
  73. for(int i = n+1; i <= tot; ++i) val[i] = G[i].size();
  74. for(int i = 1; i <= tot; ++i)
  75. if(!vis[i]) {
  76. getcnt(i, 0);
  77. S = cnt[i];
  78. dp(i, 0);
  79. }
  80. printf("%lld\n", ans*2);
  81. return 0;
  82. }

洛谷P4630 铁人两项--圆方树的更多相关文章

  1. [APIO2018] Duathlon 铁人两项 圆方树,DP

    [APIO2018] Duathlon 铁人两项 LG传送门 圆方树+简单DP. 不会圆方树的话可以看看我的另一篇文章. 考虑暴力怎么写,枚举两个点,答案加上两个点之间的点的个数. 看到题面中的一句话 ...

  2. [APIO2018]铁人两项 --- 圆方树

     [APIO2018] 铁人两项 题目大意: 给定一张图,问有多少三元组(a,b,c)(a,b,c 互不相等)满足存在一条点不重复的以a为起点,经过b,终点为c的路径 如果你不会圆方树 ------- ...

  3. [APIO2018]铁人两项 [圆方树模板]

    把这个图缩成圆方树,把方点的权值设成-1,圆点的权值设成点双的size,算 经过这个点的路径的数量*这个点的点权 的和即是答案. #include <iostream> #include ...

  4. [APIO2018]铁人两项——圆方树+树形DP

    题目链接: [APIO2018]铁人两项 对于点双连通分量有一个性质:在同一个点双里的三个点$a,b,c$,一定存在一条从$a$到$c$的路径经过$b$且经过的点只被经过一次. 那么我们建出原图的圆方 ...

  5. 【Luogu4630】【APIO2018】 Duathlon 铁人两项 (圆方树)

    Description ​ 给你一张\(~n~\)个点\(~m~\)条边的无向图,求有多少个三元组\(~(x, ~y, ~z)~\)满足存在一条从\(~x~\)到\(~z~\)并且经过\(~y~\)的 ...

  6. LOJ 2587 「APIO2018」铁人两项——圆方树

    题目:https://loj.ac/problem/2587 先写了 47 分暴力. 对于 n<=50 的部分, n3 枚举三个点,把图的圆方树建出来,合法条件是 c 是 s -> f 路 ...

  7. loj2587 「APIO2018」铁人两项[圆方树+树形DP]

    主要卡在一个结论上..关于点双有一个常用结论,也经常作为在圆方树/简单路径上的良好性质,对于任意点双内互不相同的三点$s,c,t$,都存在简单路径$s\to c\to t$,证明不会.可以参见clz博 ...

  8. [BZOJ5463][APIO2018]铁人两项(圆方树DP)

    题意:给出一张图,求满足存在一条从u到v的长度大于3的简单路径的有序点对(u,v)个数. 做了上一题[HDU5739]Fantasia(点双连通分量+DP),这个题就是一个NOIP题了. 一开始考虑了 ...

  9. 洛谷4630APIO2018铁人两项(圆方树+dp)

    QWQ神仙题啊(据说是今年第一次出现圆方树的地方) 首先根据题目,我们就是求对于每一个路径\((s,t)\)他的贡献就是两个点之间的点数,但是图上问题我并没有办法很好的解决... 这时候考虑圆方树,我 ...

随机推荐

  1. JS 的继承

    1:原生链:prototype 儿子能够继承父亲的属性,也可以觉得遗传基因不好自己改属性,(但是不能改变老爸的属性). 看例子:             function farther(){     ...

  2. 第二章 Linux目录学习

    Linux 目录结构相对windows来说更简单,Linux 目录 以 斜杠 / 为根目录,其整体结构是以/为根的树状结构. 使用 tree -L 1 查看1级目录结构 /bin 常用的二进制命令目录 ...

  3. java:数据结构(二)栈的应用(括号匹配)

    一.什么是括号匹配: 括号匹配就是利用计算机辨别表达式里面的括号是否书写成功 例如: {()((a)) }这就是一个正确 (()()   这就是一个错误的 二.括号匹配的算法: 众所周知,括号分为花括 ...

  4. 章节十、5-CSS---用CSS 通配符定位元素

    以下演示操作以该网址中的输入框为例:https://learn.letskodeit.com/p/practice 一.css样式中有三种通配符“^.$.*” 语法:tag[attribute< ...

  5. WebSocket-java实践

    websocket  主要用于  前端页面hmtl/jsp 与 后端进行socket得连接. 本例简单实现:一但后端接收到数据或者根据某些规则主动发送数据,那么可以根据不同用户等区别,发送给某个登陆得 ...

  6. zoomeye搜索+用selenium实现对佳能打印机的爬虫

    本文仅用于学习参考.要遵纪守法哦! 目的:找出一台佳能打印机,得到它的日志文件,并利用其远程打印. 1.先用zoomeye找一个打印机出来,搜索语句:printer +country:"CN ...

  7. [20190419]shared latch spin count 2.txt

    [20190419]shared latch spin count 2.txt --//上午测试shared latch XX模式的情况,链接:http://blog.itpub.net/267265 ...

  8. Windows Server 2008远程桌面默认端口更改方法

    win2008远程桌面端口默认是用的是3389端口,但是由于安全考虑,经常我们安装好系统后一般都会考虑把原来的3389端口更改为另外的端口. 本文以改为端口为25608商品为例,讲解一下具体操作过程. ...

  9. docker-compose的安装和卸载

    使用docker-compose 可以轻松.高效的管理容器,它是一个用于定义和运行多容器 docker 的应用程序工具. 原文地址:代码汇个人博客 http://www.codehui.net/inf ...

  10. Cordova入门系列(三)Cordova插件调用 转发 https://www.cnblogs.com/lishuxue/p/6018416.html

    Cordova入门系列(三)Cordova插件调用   版权声明:本文为博主原创文章,转载请注明出处 上一章我们介绍了cordova android项目是如何运行的,这一章我们介绍cordova的核心 ...