老了…稍微麻烦一点的树形DP都想不到了。

题目描述

给定一棵树,边权是整数 \(c_i\) ,找出两条不相交的链(没有公共点),使得链长的乘积最大(链长定义为这条链上所有边的权值之和,如果这条链只有 \(1\) 个点则链长视为 \(0\))。

输入输出格式

输入格式:

第一行:一个 \(n\) 表示节点个数。

接下来 \(n-1\) 行每行三个整数 \(u,v,c\) 表示 \(u,v\) 之间有一条 \(c\) 的边。

输出格式:

输出一个整数表示最大的乘积。

输入输出样例

输入样例:

  1. 5
  2. 1 2 8
  3. 2 3 -4
  4. 3 4 9
  5. 2 5 9

输出样例:

  1. 153

说明

\(2\le n\le 4\times 10^5,|c_i|\le 10^9\)

51nod 支持 int128

用法:

  1. __int128 n;

边权可能为负。

题解:

题目要求找出两条互不相交的链,使得两条链长乘积最大。

考虑到负负得正,因此需要分别找最长的两条链和最短的两条链。

trick:对于乘积最大可能产生贡献的分别是最大的两个值和最小的两个值的乘积。

考虑 DP 求直径的过程,对于两条不相交的链,我们同样可以用树形 DP,需要在每个点合并并统计答案。

首先 DFS 一遍,求出 \(f[x],g[x]\),同时,用带下划线的变量存相对最小值。则 \(f[x],f\_[x]\) 分别表示 \(x\) 的子树中连接到 \(x\) 的最长链和最短链(权值最小);\(g[x],g\_[x]\) 分别表示 \(x\) 的子树中最长链和最短链。

注意:在分析过程中只讨论最大值。编程时同时更新最小值即可。

然后我们进行“合并”操作的 DP。

考虑当前在 \(x\) 点,准备进入子树 \(A\),那么有以下几种情况:

针对上图而言,我们分析 \(x\to A\) 这条边。认为产生答案的两条链不经过这条边,那么两条链就分别在 \(A\) 和 \(B\cup C\cup \{x\}\cup R\) 中产生。

由于我们在第一次 DFS 中已经预处理过 \(g[r_A]\) 了(\(r_A\) 表示子树 \(A\) 的根),因此我们需要计算另一半集合贡献的答案 \(G\)。

因此另一半集合产生的答案可能是形如 \(f[r_B]+f[r_C]\) 这样拼起来的;也有可能是现成的,形如 \(g[r_B]\)。

此时我们需要求除了 \(A\) 以外的信息。根据贪心我们知道一定要选最大的两个(或最小的两个),但是不能选即将进去的 \(A\) 。如果我们遍历 \(x\) 除了 \(A\) 的子树的话,复杂度又不对了。因此我们需要对每一个 \(x\) 连出去的边维护前三大的 \(f\) 和前两大的 \(g\)。

另外,我发现了一种维护它们的方便做法,常数是 \(10\) 左右。假设我们用 \(s[4]\) 存下前三大值,那么每次更新时把要更新的值放在 \(s[3]\) 的位置,然后执行 std::sort(s,s+4),此时前三大值就在 \(s[0]\sim s[2]\) 里了。当然这是一种代码量小的写法,可能会有一些常数更优秀的做法。

但是 \(f[fa_x],g[fa_x](fa_x\in R)\) 等信息是没有处理的,因为它不是子树,那么我们就需要在第二次 dfs 的过程中在线更新 \(x​\) 父亲方向的答案。

每次进入一棵新的子树时,\(g[x]\) 可以更新为子树外的答案,即上文提到的 \(G\)。而 \(f[x]\) 维护的是一条链,那么只能从子树外的 \(f\) 更新过来。

但是由于 \(n\le 400\ 000\),容易爆栈,所以需要一点卡空间的姿势。或者从 \(\frac n2\) 开始 dfs 可以解决某些玄学问题。

而且说起来简单,由于最大值最小值都要维护,代码量还是很大的。

时间复杂度 \(O(n)\)。

Code:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<algorithm>
  4. #define ll long long
  5. #define lll __int128
  6. #define maintain() sort(s[x],s[x]+4,cmp),sort(s_[x],s_[x]+4),sort(t[x],t[x]+3,cmp),sort(t_[x],t_[x]+3);
  7. using std::sort;
  8. bool cmp(ll x,ll y){return x>y;}
  9. ll Max(ll x,ll y){return x>y?x:y;}
  10. ll Min(ll x,ll y){return x<y?x:y;}
  11. struct edge
  12. {
  13. int n,nxt,v;
  14. edge(int n,int nxt,int v)
  15. {
  16. this->n=n;
  17. this->nxt=nxt;
  18. this->v=v;
  19. }
  20. edge(){}
  21. }e[800100];
  22. int head[400100],ecnt=-1;
  23. void add(int from,int to,int v)
  24. {
  25. e[++ecnt]=edge(to,head[from],v);
  26. head[from]=ecnt;
  27. e[++ecnt]=edge(from,head[to],v);
  28. head[to]=ecnt;
  29. }
  30. ll f[400100],g[400100];
  31. ll f_[400100],g_[400100];
  32. //第一次 dfs 求出 f,g
  33. //f[x] 表示 x 子树中伸出来的最大值
  34. //g[x] 表示 x 树内的最大值
  35. //_ 表示最小值
  36. void Dfs(int x,int from)
  37. {
  38. for(int i=head[x];~i;i=e[i].nxt)
  39. if(e[i].n!=from)
  40. {
  41. Dfs(e[i].n,x);
  42. ll p=f[e[i].n]+e[i].v,p_=f_[e[i].n]+e[i].v;
  43. g[x]=Max(Max(g[x],f[x]+p),g[e[i].n]);
  44. g_[x]=Min(Min(g_[x],f_[x]+p_),g_[e[i].n]);
  45. f[x]=Max(f[x],p);
  46. f_[x]=Min(f_[x],p_);
  47. }
  48. g[x]=g[x]>f[x]?g[x]:f[x];
  49. g_[x]=g_[x]<f_[x]?g_[x]:f_[x];
  50. }
  51. lll ans=0;
  52. ll s[400100][4],s_[400100][4];//最大两个 f
  53. ll t[400100][3],t_[400100][3];//最大两个 g
  54. ll Gg,Gg_;//维护子树中最大的 g
  55. ll F,G,F_,G_;//相当于 f[fa[x]]
  56. void dfs(int x,int from,int h)//h 是来边
  57. {
  58. ans=ans>(lll)G*g[x]?ans:(lll)G*g[x];
  59. ans=ans>(lll)G_*g_[x]?ans:(lll)G_*g_[x];
  60. //F 更新 G
  61. ll gg=Max(G,F);//在本层递归中存的 G
  62. ll gg_=Min(G_,F_);
  63. F+=h;
  64. F_+=h;
  65. for(int i=0;i<=3;++i)
  66. s[x][i]=s_[x][i]=0;
  67. for(int i=0;i<=2;++i)
  68. t[x][i]=t_[x][i]=0;//清零
  69. s[x][3]=F;//更新父亲方向的信息
  70. s_[x][3]=F_;
  71. t[x][2]=G;
  72. t_[x][2]=G_;
  73. maintain();
  74. ll ff=F,ff_=F_;
  75. for(int i=head[x];~i;i=e[i].nxt)
  76. if(e[i].n!=from)
  77. {
  78. s[x][3]=f[e[i].n]+e[i].v;
  79. s_[x][3]=f_[e[i].n]+e[i].v;
  80. t[x][2]=g[e[i].n];
  81. t_[x][2]=g_[e[i].n];
  82. maintain();
  83. }
  84. for(int i=head[x];~i;i=e[i].nxt)
  85. if(e[i].n!=from)
  86. {
  87. if(g[e[i].n]==t[x][0])//更新子树中 g 的信息
  88. Gg=t[x][1];
  89. else
  90. Gg=t[x][0];
  91. if(g_[e[i].n]==t_[x][0])
  92. Gg_=t_[x][1];
  93. else
  94. Gg_=t_[x][0];
  95. if(f[e[i].n]+e[i].v==s[x][0])//需要排除即将进去的子树
  96. {
  97. G=Max(Max(gg,s[x][1]+s[x][2]),Gg);
  98. F=Max(ff,s[x][1]);
  99. }
  100. else if(f[e[i].n]+e[i].v==s[x][1])
  101. {
  102. G=Max(Max(gg,s[x][0]+s[x][2]),Gg);
  103. F=Max(ff,s[x][0]);
  104. }
  105. else
  106. {
  107. G=Max(Max(gg,s[x][0]+s[x][1]),Gg);
  108. F=Max(ff,s[x][0]);
  109. }
  110. if(f_[e[i].n]+e[i].v==s_[x][0])//还要更新F
  111. {
  112. G_=Min(Min(gg_,s_[x][1]+s_[x][2]),Gg_);
  113. F_=Min(ff_,s_[x][1]);
  114. }
  115. else if(f_[e[i].n]+e[i].v==s_[x][1])
  116. {
  117. G_=Min(Min(gg_,s_[x][0]+s_[x][2]),Gg_);
  118. F_=Min(ff_,s_[x][0]);
  119. }
  120. else
  121. {
  122. G_=Min(Min(gg_,s_[x][0]+s_[x][1]),Gg_);
  123. F_=Min(ff_,s_[x][0]);
  124. }
  125. dfs(e[i].n,x,e[i].v);
  126. }
  127. }
  128. int main()
  129. {
  130. memset(head,-1,sizeof(head));
  131. int n,u,v,w;
  132. scanf("%d",&n);
  133. for(int i=1;i<n;++i)
  134. {
  135. scanf("%d%d%d",&u,&v,&w);
  136. add(u,v,w);
  137. }
  138. Dfs(n>>1,0);
  139. dfs(n>>1,0,0);
  140. if(!ans)//注意用栈输出时为 0 的情况
  141. puts("0");
  142. int stk[100],tp=0;
  143. while(ans)
  144. {
  145. stk[++tp]=ans%10;
  146. ans/=10;
  147. }
  148. while(tp)
  149. printf("%d",stk[tp--]);
  150. return 0;
  151. }

51nod 1812 树的双直径 题解【树形DP】【贪心】的更多相关文章

  1. 51Nod - 1405 树的距离之和(树形DP)

    1405 树的距离之和 题意 给定一棵无根树,假设它有n个节点,节点编号从1到n,求任意两点之间的距离(最短路径)之和. 分析 树形DP. 首先我们让 \(1\) 为根.要开两个数组 \(up \ d ...

  2. 【bzoj4027】[HEOI2015]兔子与樱花 树形dp+贪心

    题目描述 很久很久之前,森林里住着一群兔子.有一天,兔子们突然决定要去看樱花.兔子们所在森林里的樱花树很特殊.樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1个树枝连接,我们可以把它 ...

  3. BZOJ_3124_[Sdoi2013]直径_树形DP

    BZOJ_3124_[Sdoi2013]直径_树形DP Description 小Q最近学习了一些图论知识.根据课本,有如下定义.树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度.如果一棵 ...

  4. 洛谷P2507 [SCOI2008]配对 题解(dp+贪心)

    洛谷P2507 [SCOI2008]配对 题解(dp+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/1299251 链接题目地址:洛谷P2507 [S ...

  5. Codeforces 348E 树的中心点的性质 / 树形DP / 点分治

    题意及思路:http://ydc.blog.uoj.ac/blog/12 在求出树的直径的中心后,以它为根,对于除根以外的所有子树,求出子树中的最大深度,以及多个点的最大深度的lca,因为每个点的最长 ...

  6. 【BZOJ2286】消耗战(虚树,DFS序,树形DP)

    题意:一棵N个点的树上有若干个关键点,每条边有一个边权,现在要将这些关键点到1的路径全部切断,切断一条边的代价就是边权. 共有M组询问,每组询问有k[i]个关键点,对于每组询问求出完成任务的最小代价. ...

  7. 【BZOJ3611】大工程(虚树,DFS序,树形DP)

    题意:有一棵树,树有边权,有若干次询问,给出一些点,求: 1.这些点互相之间的距离之和 2.点对距离中的最大和最小值 n<=1000000 q<=50000并且保证所有k之和<=2* ...

  8. hdu 2242双联通分量+树形dp

    /*先求出双联通缩点,然后进行树形dp*/ #include<stdio.h> #include<string.h> #include<math.h> #defin ...

  9. 【2019.8.20 NOIP模拟赛 T2】小B的树(tree)(树形DP)

    树形\(DP\) 考虑设\(f_{i,j,k}\)表示在\(i\)的子树内,从\(i\)向下的最长链长度为\(j\),\(i\)子树内直径长度为\(k\)的概率. 然后我们就能发现这个东西直接转移是几 ...

随机推荐

  1. Scala入门学习随笔

    推荐学习视频:慕课网http://www.imooc.com/learn/613,讲师:辰风 ScalaAPI:http://www.scala-lang.org/api/current/#packa ...

  2. vue项目引入第三方js插件,单个js文件引入成功,使用该插件方法时报错(问题已解决)

    1.引入第三方js文件,npm安装不了 2.控制台显示引入成功 3.在methods下使用 图片看不清请看下面代码 updateTime() { setInterval(()=>{ var cd ...

  3. C++中的深拷贝和浅拷贝 QT中的深拷贝,浅拷贝和隐式共享

    下面是C++中定义的深,浅拷贝 当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用.也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用.以下情况都会 ...

  4. (4)WePHP 模板引入CSS js

    模板有两个定义了两个常量 父类已经定义好了 //模板常量 $dirStr=dirname($_SERVER['SCRIPT_NAME']); $dirStr=$dirStr=='\\'?NULL:$d ...

  5. javascript总结48:正则表达式(RegExp)

    1 正则表达式(英语:Regular Expression) 正则表达式(英语:Regular Expression,在代码中常简写为regex.regexp或RE)使用单个字符串来描述.匹配一系列符 ...

  6. bt协议详解 基础篇(上)

    bt协议详解 基础篇(上) 最近开发了一个免费教程的网站,产生了仔细了解bt协议的想法,所以写了这一篇文章,后续还会写一些关于搜索和索引的东西,都是在开发这个网站的过程中学习到的技术,敬请期待. 1 ...

  7. freePCRF免费版体验

    [摘要]遍寻网络数昼夜,未得开源PCRF,亦未得有参考价值的PCRF相关文档.所幸觅得免费体验版freePCRF软件.可窥见PCRF设计思路.方法:PCC规则定义.管理策略:遂记录安装.体验心得. f ...

  8. Lotus迁移到Exchange 2010 POC 之Domino Server的配置!

    1.      在桌面点击安装完成的Domino 服务器配置:

  9. jmeter - 命令行方式运行

    命令格式: jmeter -n -t <testplan filename> -l <listener filename> 参数说明: -n 非 GUI 模式 -> 在非 ...

  10. android 中 dp和px转换

    DisplayUtils代码: public class DisplayUtil { public static int px2dip(Context context, float px) { flo ...