题意:树上最长不相交k条链。

 #include <cstdio>
#include <algorithm>
#include <cstring> typedef long long LL;
const int N = ; struct Edge {
int nex, v;
LL len;
}edge[N << ]; int top; LL f[N][][];
int e[N], n, k, siz[N]; inline void add(int x, int y, LL z) {
top++;
edge[top].v = y;
edge[top].len = z;
edge[top].nex = e[x];
e[x] = top;
return;
} inline void exmax(LL &a, LL b) {
if(a < b) {
a = b;
}
return;
} void DFS(int x, int fa) {
siz[x] = ;
f[x][][] = ;
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
if(y == fa) {
continue;
}
DFS(y, x);
siz[x] += siz[y];
// DP
for(int j = std::min(k, siz[x]); j >= ; j--) {
for(int p = ; p <= j && p <= siz[y]; p++) { // p in son
exmax(f[x][j][], f[x][j - p + ][] + f[y][p][] + edge[i].len);
exmax(f[x][j][], f[x][j - p][] + std::max(f[y][p][], std::max(f[y][p][], f[y][p][])));
exmax(f[x][j][], f[x][j - p][] + std::max(f[y][p][], std::max(f[y][p][], f[y][p][])));
exmax(f[x][j][], f[x][j - p][] + std::max(f[y][p][], std::max(f[y][p][], f[y][p][])));
exmax(f[x][j][], f[x][j - p][] + f[y][p][] + edge[i].len);
}
}
}
for(int i = ; i <= k && i <= siz[x]; i++) {
exmax(f[x][i][], f[x][i - ][]);
}
/*printf("x = %d \n", x);
for(int i = 0; i <= k && i <= siz[x]; i++) {
printf("%d || 0 : %lld 1 : %lld 2 : %lld \n", i, f[x][i][0], f[x][i][1], f[x][i][2]);
}*/
return;
} int main() {
int n;
memset(f, ~0x3f, sizeof(f));
scanf("%d%d", &n, &k);
k++;
int x, y;
LL z;
for(int i = ; i < n; i++) {
scanf("%d%d%lld", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
DFS(, );
printf("%lld\n", std::max(std::max(f[][k][], f[][k][]), f[][k][]));
return ;
}

60分DP

解:带权二分/wqs二分/DP凸优化。

一个比较常见的套路吧。

一般是求解有k限制的最优化问题。且随着k的变化,极值函数上凸/下凸。

这时我们二分一个斜率去切它,会有一个斜率切到我们要的k。

感性理解一下,我们给这k个事物附上权值,然后权值增加的时候k就会变多,权值减小(可以为负)的时候k会变少。

然后会有某个权值使得不限制k时的最优值恰好选了k。这时候我们减去附加的权值即可。

本题就是给每条链加上一个权值。

还有一道题是k条白边,剩下的选黑边的最小生成树。

 #include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath> typedef long long LL;
const int N = ;
const LL INF = 1e17; struct Edge {
int nex, v;
LL len;
}edge[N << ]; int top; LL f[N][], D;
int g[N][], n, k, e[N]; inline void add(int x, int y, LL z) {
top++;
edge[top].v = y;
edge[top].len = z;
edge[top].nex = e[x];
e[x] = top;
return;
} inline void exmax(LL &a, int &c, LL b, int d) {
if(a < b || (a == b && c > d)) {
a = b;
c = d;
}
return;
} void DFS(int x, int fa) {
f[x][] = ; // 初始化 不选链
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
if(y == fa) {
continue;
}
DFS(y, x);
exmax(f[x][], g[x][], f[x][] + f[y][], g[x][] + g[y][]);
exmax(f[x][], g[x][], f[x][] + f[y][] + edge[i].len - D, g[x][] + g[y][] - ); // 1 -> 2 exmax(f[x][], g[x][], f[x][] + f[y][], g[x][] + g[y][]);
exmax(f[x][], g[x][], f[x][] + f[y][] + edge[i].len, g[x][] + g[y][]); // 0 -> 1 exmax(f[x][], g[x][], f[x][] + f[y][], g[x][] + g[y][]);
}
exmax(f[x][], g[x][], f[x][] + D, g[x][] + ); // 自己单独开链
exmax(f[x][], g[x][], f[x][], g[x][]);
exmax(f[x][], g[x][], f[x][], g[x][]);
return;
} inline bool check(LL mid) {
D = mid;
memset(g, , sizeof(g));
memset(f, ~0x3f, sizeof(f));
DFS(, );
//printf("D = %lld \nf = %lld g = %d \n\n", D, f[1][2], g[1][2]);
return ;
} int main() { scanf("%d%d", &n, &k);
LL z, r = , l;
k++;
for(int i = , x, y; i < n; i++) {
scanf("%d%d%lld", &x, &y, &z);
add(x, y, z);
add(y, x, z);
r += std::abs(z);
}
l = -r;
while(l < r) {
LL mid = (l + r + ) >> ;
//printf("%lld %lld mid = %lld \n", l, r, mid);
check(mid);
if(g[][] == k) {
printf("%lld\n", f[][] - k * mid);
return ;
}
if(g[][] > k) {
r = mid - ;
}
else {
l = mid;
}
} check(r);
printf("%lld\n", f[][] - k * r);
return ;
}

AC代码

本题只要整数二分就行了。

负数二分用右移,是向下取整。

细节:可能最优点不是凸包上的顶点,是一条边中间。我们这时找到靠左的那个顶点,然后用这个斜率 * k就行了。

实数版:

 #include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath> typedef long long LL;
const int N = ;
const LL INF = 1e17;
const double eps = 1e-; struct Edge {
int nex, v;
LL len;
}edge[N << ]; int top; double f[N][], D;
int g[N][], n, k, e[N]; inline void add(int x, int y, LL z) {
top++;
edge[top].v = y;
edge[top].len = z;
edge[top].nex = e[x];
e[x] = top;
return;
} inline void exmax(double &a, int &c, double b, int d) {
// if(a < b || (a == b && c > d)) {
if(a < b) {
a = b;
c = d;
}
return;
} void DFS(int x, int fa) {
f[x][] = ; // 初始化 不选链
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
if(y == fa) {
continue;
}
DFS(y, x);
exmax(f[x][], g[x][], f[x][] + f[y][], g[x][] + g[y][]);
exmax(f[x][], g[x][], f[x][] + f[y][] + edge[i].len - D, g[x][] + g[y][] - ); // 1 -> 2 exmax(f[x][], g[x][], f[x][] + f[y][], g[x][] + g[y][]);
exmax(f[x][], g[x][], f[x][] + f[y][] + edge[i].len, g[x][] + g[y][]); // 0 -> 1 exmax(f[x][], g[x][], f[x][] + f[y][], g[x][] + g[y][]);
}
exmax(f[x][], g[x][], f[x][] + D, g[x][] + ); // 自己单独开链
exmax(f[x][], g[x][], f[x][], g[x][]);
exmax(f[x][], g[x][], f[x][], g[x][]);
return;
} inline bool check(double mid) {
D = mid;
memset(g, , sizeof(g));
//memset(f, ~0x3f, sizeof(f));
for(int i = ; i <= n; i++) {
f[i][] = f[i][] = f[i][] = -INF;
}
DFS(, );
//printf("D = %lld \nf = %lld g = %d \n\n", D, f[1][2], g[1][2]);
return ;
} int main() { scanf("%d%d", &n, &k);
LL z;
double r = , l;
k++;
for(int i = , x, y; i < n; i++) {
scanf("%d%d%lld", &x, &y, &z);
add(x, y, z);
add(y, x, z);
r += std::abs(z);
}
l = -r;
while(fabs(r - l) > eps) {
double mid = (l + r) / ;
//printf("%lld %lld mid = %lld \n", l, r, mid);
check(mid);
if(g[][] == k) {
printf("%.0f\n", f[][] - k * mid);
return ;
}
if(g[][] > k) {
r = mid;
}
else {
l = mid;
}
} check(r);
printf("%.0f\n", f[][] - k * r);
return ;
}

AC代码

洛谷P4383 林克卡特树的更多相关文章

  1. P4383 [八省联考2018]林克卡特树 树形dp Wqs二分

    LINK:林克卡特树 作为树形dp 这道题已经属于不容易的级别了. 套上了Wqs二分 (反而更简单了 大雾 容易想到还是对树进行联通情况的dp 然后最后结果总和为各个联通块内的直径. \(f_{i,j ...

  2. 【BZOJ5252】林克卡特树(动态规划,凸优化)

    [BZOJ5252]林克卡特树(动态规划,凸优化) 题面 BZOJ(交不了) 洛谷 题解 这个东西显然是随着断开的越来越多,收益增长速度渐渐放慢. 所以可以凸优化. 考虑一个和\(k\)相关的\(dp ...

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

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

  4. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  5. [八省联考2018]林克卡特树lct——WQS二分

    [八省联考2018]林克卡特树lct 一看这种题就不是lct... 除了直径好拿分,别的都难做. 所以必须转化 突破口在于:连“0”边 对于k=0,我们求直径 k=1,对于(p,q)一定是从p出发,走 ...

  6. [BZOJ 5252][LOJ 2478][九省联考2018] 林克卡特树

    [BZOJ 5252][LOJ 2478][九省联考2018] 林克卡特树 题意 给定一个 \(n\) 个点边带权的无根树, 要求切断其中恰好 \(k\) 条边再连 \(k\) 条边权为 \(0\) ...

  7. 【BZOJ2830/洛谷3830】随机树(动态规划)

    [BZOJ2830/洛谷3830]随机树(动态规划) 题面 洛谷 题解 先考虑第一问. 第一问的答案显然就是所有情况下所有点的深度的平均数. 考虑新加入的两个点,一定会删去某个叶子,然后新加入两个深度 ...

  8. luoguP4383 [八省联考2018]林克卡特树(树上dp,wqs二分)

    luoguP4383 [八省联考2018]林克卡特树(树上dp,wqs二分) Luogu 题解时间 $ k $ 条边权为 $ 0 $ 的边. 是的,边权为零. 转化成选正好 $ k+1 $ 条链. $ ...

  9. 洛谷P3655 差分数组 树状数组

    题目链接:https://www.luogu.org/problemnew/show/P3655 不一定对,仅供参考,不喜勿喷,不喜勿喷. 先copy洛谷P3368 [模板]树状数组 2 题解里面一位 ...

随机推荐

  1. 身在上海的她,该不该继续"坚持"前端开发?

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 一 对于目前的IT行业,我实在不想她还没在这个行业中站稳脚跟就开始有 ...

  2. MySQL主主同步配置

    1. MySQL主主配置过程 在上一篇实现了主从同步的基础上,进行主主同步的配置. 这里用node19(主),node20(从)做修改,使得node19和node20变为主主同步配置模式 修改配置文件 ...

  3. Redis哨兵模式(sentinel)学习总结及部署记录(主从复制、读写分离、主从切换)

    Redis的集群方案大致有三种:1)redis cluster集群方案:2)master/slave主从方案:3)哨兵模式来进行主从替换以及故障恢复. 一.sentinel哨兵模式介绍Sentinel ...

  4. Linux内核分析 读书笔记 (第七章)

    第七章 链接 1.链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行. 2.链接可以执行于编译时,也就是在源代码被翻译成机器代码时:也可以执行于 ...

  5. Linux内核 实践二

    实践二 内核模块编译 20135307 张嘉琪 一.实验原理 Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合.之所以提供模块机制,是因为Linux本身是一个单内核.单内核由于所有内容 ...

  6. Xcode自动选择证书

    从xcode3时代习惯了手动选择证书,即 Provisioning Profile和 Code Signing Identify. 而随着团队扩大,应用量增多,需要管理的证书也越来越多,每次从长长的l ...

  7. Maven入门指南④:仓库

    1 . 仓库简介 没有 Maven 时,项目用到的 .jar 文件通常需要拷贝到 /lib 目录,项目多了,拷贝的文件副本就多了,占用磁盘空间,且难于管理.Maven 使用一个称之为仓库的目录,根据构 ...

  8. spring 注入DI

    web  项目的搭建  以注入对象的方式

  9. ESXi去掉 SSH已经启用的警告信息

    1. 在vCenter管理的机器里面 总是有几台服务器 提示 SSH启动连接 并且有黄色的警告信息 有时内存或者CPU报警的信息就看不到了.. 所以想着解决他,经过百度发现解决办法为: 选中host主 ...

  10. 基于C#.NET的高端智能化网络爬虫(一)(反爬虫哥必看)

    前两天朋友发给我了一篇文章,是携程网反爬虫组的技术经理写的,大概讲的是如何用他的超高智商通过(挑衅.怜悯.嘲讽.猥琐)的方式来完美碾压爬虫开发者.今天我就先带大家开发一个最简单低端的爬虫,突破携程网超 ...