这道题我前前后后做了一年,共过了 4 4 4 遍,每次都有的新的理解;这次我认为自己理解透了,于是就写了一篇题解。

这道题是我入坑看到的第一道黑题(当时很萌,不知道黑题是什么,看到这题感觉很好玩),另外还有就是《切树游戏》和 Spiders Evil Plan,记载着我的回忆(

Description

传送门

Solution

算法一

为方便叙述,令树根为 1 1 1, w u , v w_{u,v} wu,v​ 表示 u , v u,v u,v 之间的边权, son { u } \text{son}\{u\} son{u} 表示 u u u 的儿子组成的集合。

可以发现,答案即为 ⌈ \lceil ⌈ 在树上选出 k + 1 k+1 k+1 条不相交的路径 ⌋ \rfloor ⌋ 的最大边权和。

考虑 dp \text{dp} dp。

令 f u , i , j f_{u,i,j} fu,i,j​ 表示,看了以 u u u 为根的子树,子树内一共选了 i i i 条不相交的路径,且 u u u 当前度数为 j j j 的最大边权和。

  • 若 j = 0 j=0 j=0,则目前没有包含 u u u 的路径。特别的,若以 u u u 为根的子树已经遍历完,则 f u , i , j f_{u,i,j} fu,i,j​ 表示所有该子树中的路径都被固定时的最大边权和(也就是说,遍历完子树后 f u , 0 f_{u,0} fu,0​ 对应的状态中 u u u 的度数可以为 0 0 0 或 1 1 1 或 2 2 2)。
  • 若 j = 1 j=1 j=1,则目前恰有一条以 u u u 为一端的非固定路径(也就是说可以继续延伸出去,没有彻底固定这条路径的形态)。因为这条路径不固定,所以这条路径并没有被计入 i i i。
  • 若 j = 2 j=2 j=2,则目前有一条包含 u u u 的路径,且其任意一端均不为 u u u(跨越了 u u u 的两个子树)。

考虑使用树形背包转移。具体来说,令目前要将以 v ( v ∈ son { u } ) v(v \in \text{son}\{u\}) v(v∈son{u}) 为根的子树合并上来,则有转移:

f u , i , 0 : = max ⁡ ( f u , i , 0 , max ⁡ j = 0 i { f u , j , 0 + f v , i − j , 0 } ) f_{u,i,0}:=\max(f_{u,i,0},\max_{j=0}^i \{f_{u,j,0}+f_{v,i-j,0}\}) fu,i,0​:=max(fu,i,0​,j=0maxi​{fu,j,0​+fv,i−j,0​}) f u , i , 1 : = max ⁡ ( f u , i , 1 , max ⁡ j = 0 i { f u , j , 0 + f v , i − j , 1 + w u , v } , max ⁡ j = 0 i { f u , j , 1 + f v , i − j , 0 } ) f_{u,i,1}:=\max(f_{u,i,1},\max_{j=0}^i \{f_{u,j,0}+f_{v,i-j,1}+w_{u,v}\},\max_{j=0}^i \{f_{u,j,1}+f_{v,i-j,0}\}) fu,i,1​:=max(fu,i,1​,j=0maxi​{fu,j,0​+fv,i−j,1​+wu,v​},j=0maxi​{fu,j,1​+fv,i−j,0​}) f u , i , 2 : = max ⁡ ( f u , i , 2 , max ⁡ j = 0 i − 1 { f u , j , 1 + f v , i − j − 1 , 1 + w u , v } , max ⁡ j = 0 i { f u , j , 2 + f v , i − j , 0 } ) f_{u,i,2}:=\max(f_{u,i,2},\max_{j=0}^{i-1} \{f_{u,j,1}+f_{v,i-j-1,1}+w_{u,v}\},\max_{j=0}^i \{f_{u,j,2}+f_{v,i-j,0}\}) fu,i,2​:=max(fu,i,2​,j=0maxi−1​{fu,j,1​+fv,i−j−1,1​+wu,v​},j=0maxi​{fu,j,2​+fv,i−j,0​})

在转移结束后,执行:

f u , i , 0 : = max ⁡ ( f u , i , 0 , f u , i − 1 , 1 , f u , i , 2 ) f_{u,i,0}:=\max(f_{u,i,0},f_{u,i-1,1},f_{u,i,2}) fu,i,0​:=max(fu,i,0​,fu,i−1,1​,fu,i,2​)

答案即为 f 1 , k + 1 , 0 f_{1,k+1,0} f1,k+1,0​。

这个做法的时间复杂度是 O ( n k ) O(nk) O(nk) 而非 O ( n k 2 ) O(nk^2) O(nk2) 或 O ( n 2 ) O(n^2) O(n2) 的,期望得分 60 60 60 分。下面是来自我说说的简要证明:

  • 每次合并,将第一维度从 0 0 0 枚举到了 m i n ( s i z [ u ] , k ) min(siz[u],k) min(siz[u],k),第二维度从 0 0 0 枚举到了 m i n ( s i z [ v ] , k ) min(siz[v],k) min(siz[v],k),那么其对复杂度的贡献就是它们的乘积。
  • 我们可以认为,这里将 ⌈ \lceil ⌈ 目前已合并到 u u u 处的部分中 dfs \text{dfs} dfs 序前 k k k 大的 ⌋ \rfloor ⌋ 和 ⌈ \lceil ⌈ 以 v v v 为根的子树中 dfs \text{dfs} dfs 序前 k 小的 ⌋ \rfloor ⌋ 两两合并。
  • 因此,从全局的角度来看,每个节点只会与 dfs \text{dfs} dfs 序与其差不超过 2 k 2k 2k 的点进行合并。
  • 从而,总复杂度为 O ( 4 n k ) O(4nk) O(4nk)。

算法二

运气足够好可以发现, f 1 , 0 , f 1 , 1 , ⋯ , f 1 , k f_{1,0},f_{1,1},\cdots,f_{1,k} f1,0​,f1,1​,⋯,f1,k​ 组成了一个上凸函数。

于是,直接 wqs 二分就可以 O ( n log ⁡ w ) O(n \log w) O(nlogw) 地通过本题了,尽管常数大得离谱。

这题就这么结束了?不,我们还需要注意一个细微的问题——凸函数上三点共线

这会意味着什么呢?为什么会导致错误呢?考虑一段区间 [ l , r ] [l,r] [l,r],其中的点( f 1 , l , f 1 , l + 1 , ⋯ , f 1 , r f_{1,l},f_{1,l+1},\cdots,f_{1,r} f1,l​,f1,l+1​,⋯,f1,r​)在一条直线上。那么,若 k ∈ [ l , r ] k \in [l,r] k∈[l,r],那么很容易出现切不到 k k k 的情况(即,虽然斜率正确,但是切到的点不对),导致 wqs 二分结束了都没有输出答案,从而导致 WA。

那么该如何处理呢?可以发现,在 wqs 二分结束后,若没有找出答案,那么我们依然可以得到该线的斜率截距,其中前者即为二分结束后的下界 l l l,后者可以通过再跑一轮 dp \text{dp} dp 得出。于是,我们不难确定这个一次函数的表达式,就能简单地得出该一次函数在 k k k 处的值了。

综上所述,我们处理完了细节,本题被彻底解决。

Code

由于 dp \text{dp} dp 不仅要存储最大边权和,还要存储选出路径的条数,因此我采用了一个叫做 LCT 的结构体来维护,重载加法,小于之后十分方便,这里建议大家采用。

另外,请注意开 long long,并把二分上下界的绝对值设大。同时,二分上下界有人证明过可以设置为整数了,于是我就写了整数。虽然我觉得这是错的,所以大家还是用 double 吧。

#include <bits/stdc++.h>
#define int long long
#define chkmax(a,b) ((a)<(b)?(a)=(b):0)
using namespace std;
const int maxl=300005;int read(){int s=0,w=1;char ch=getchar();while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}return s*w;
}
int n,k,cnt,l,r;
int head[maxl];struct edge{int nxt,to,dis;}e[maxl<<1];struct LCT{int val,x;LCT(int xx=0,int yy=0):val(xx),x(yy){};friend bool operator<(LCT a,LCT b){return a.val==b.val?a.x>b.x:a.val<b.val;}friend LCT operator+(LCT a,LCT b){return LCT(a.val+b.val,a.x+b.x);}friend LCT operator+(LCT a,int b){return LCT(a.val+b,a.x);}
}f[maxl][3],s;void add_edge(int u,int v,int w){cnt++;e[cnt].to=v,e[cnt].dis=w,e[cnt].nxt=head[u],head[u]=cnt;
}void dfs(int u,int fath){f[u][0]=f[u][1]=f[u][2]=LCT();for (int i=head[u];i;i=e[i].nxt){int v=e[i].to;if (v==fath)  continue;dfs(v,u);chkmax(f[u][2],max(f[u][2]+f[v][0],f[u][1]+f[v][1]+s+e[i].dis));chkmax(f[u][1],max(f[u][1]+f[v][0],f[u][0]+f[v][1]+e[i].dis));chkmax(f[u][0],f[u][0]+f[v][0]);}chkmax(f[u][0],max(f[u][1]+s,f[u][2]));
}signed main(){n=read(),k=read()+1;for (int i=1;i<n;i++){int u=read(),v=read(),w=read();add_edge(u,v,w),add_edge(v,u,w);}l=-3e11,r=3e11;while (l<=r){int mid=(l+r)>>1;s=LCT(-mid,1);dfs(1,0);if (f[1][0].x==k)  return cout<<f[1][0].val+k*mid<<endl,0;else if (f[1][0].x>k)  l=mid+1;else r=mid-1;}s=LCT(-l,1),dfs(1,0);cout<<f[1][0].val+k*l<<endl;return 0;
}

[八省联考 2018] 林克卡特树 题解相关推荐

  1. LuoguP4383 [八省联考2018]林克卡特树lct

    LuoguP4383 [八省联考2018]林克卡特树lct https://www.luogu.org/problemnew/show/P4383 分析: 题意等价于选择\(K\)条点不相交的链,使得 ...

  2. luogu4383 bzoj5252[八省联考2018]林克卡特树lct

    ** [八省联考2018]林克卡特树lct** luogu bzoj 分析 很神仙的一道wqs二分.是真的不会切>-< 如果已经切完了,最优秀的方案就是每个联通块搞直径然后连起来一定是最优 ...

  3. P4383 [八省联考2018]林克卡特树(树形dp+wqs二分)

    [八省联考2018]林克卡特树 题目大意:给定一棵有负权边的树,现在必须恰好删去 k k k条边,并加上恰好 k k k条权值为 0 0 0的边,要求最大化它的直径长度. 首先考虑删去 K K K条边 ...

  4. [八省联考2018]林克卡特树

    林克卡特树 题解 挺简单的一道题. 原题断 k k k条边连 k k k条边权为 0 0 0的边相当于寻去 k + 1 k+1 k+1条不相交链出来,将它们连上得到的结果. 所以我们要从原树中选取 k ...

  5. 洛谷P4383 [八省联考2018]林克卡特树lct(DP凸优化/wqs二分)

    题目描述 小L 最近沉迷于塞尔达传说:荒野之息(The Legend of Zelda: Breath of The Wild)无法自拔,他尤其喜欢游戏中的迷你挑战. 游戏中有一个叫做"LC ...

  6. 洛谷.4383.[八省联考2018]林克卡特树lct(树形DP 带权二分)

    题目链接 \(Description\) 给定一棵边带权的树.求删掉K条边.再连上K条权为0的边后,新树的最大直径. \(n,K\leq3\times10^5\). \(Solution\) 题目可以 ...

  7. P4383 [八省联考 2018] 林克卡特树(wqs二分、树形dp)

    解析 它还真的不难. 乐. 这题没做出来有些谔谔. 外层wqs二分显而易见,里面不知道为啥我总觉得这个题可以贪心. 然后一直试图在原树直径上下功夫,一筹莫展. 看到题解"dp"两个 ...

  8. P4383 [八省联考2018]林克卡特树lct 树形DP+凸优化/带权二分

    $ \color{#0066ff}{ 题目描述 }$ 小L 最近沉迷于塞尔达传说:荒野之息(The Legend of Zelda: Breath of The Wild)无法自拔,他尤其喜欢游戏中的 ...

  9. [八省联考2018]林克卡特树lct

    题面在这里 description 一个\(N\)个点的\(Tree\),每条边有一个整数边权\(v_i\),表示走这条边会获得\(v_i\)的收益: 小\(L\)需要控制主角\(Link\),\(C ...

最新文章

  1. 排列(permutation)2_6
  2. maven pom java版本_Maven更新POM中的JDK版本(比如更新为JDK1.8)
  3. 浩鲸科技携手阿里云原生共同打造“场域运营数字化解决方案”
  4. [leetcode]Binary Tree Inorder Traversal
  5. 《FusionCharts学习及使用笔记》之 第一篇
  6. 需要规范日志格式_Node开发的日志规范
  7. Gstreamer 搭建RTSP服务器(九)
  8. JavaScript 启动性能瓶颈分析与解决方案
  9. Uboot下SPI FLASH的添加(SPI 控制器采用软件模拟的方式)
  10. the7主题中文版升级到v.6.7.1(2018年7月27日)
  11. ajax的三种传参方式
  12. html中的项目符号和编号,CSS重新定义项目符号和编号
  13. 服务器上搭建Lepus——开源的数据库监控系统
  14. 90% 都会的 ES6 简化代码技巧,你用过哪些?
  15. python抢票开发——设备预约助手实现
  16. samba服务器介绍
  17. 【UML】——活动图
  18. gr java ch,Apache POI 快速指南.pdf
  19. Jquery 添加删除属性、添加删除class、添加删除Css
  20. 矩形波发生器c语言,矩形波发生器电路设计方案汇总(六款模拟电路设计原理图详解)...

热门文章

  1. Edge浏览器移除桔梗网页的方法
  2. 前端面试题之JavaScript【this指向】
  3. Python GUI之tkinter(一)
  4. 微信小程序转QQ小程序
  5. atcoder abc248
  6. markdown首行缩进两种方式
  7. Pytorch实现卷积运算(互相关)
  8. 子元素定位后,无法撑开父元素
  9. QML 中神秘的 Component
  10. QQ宠物玩结婚生个宠物小宝贝送Q友(转)