题意:树上最长不相交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. 详解javascript中this的工作原理

    在 JavaScript 中 this 常常指向方法调用的对象,但有些时候并不是这样的,本文将详细解读在不同的情况下 this 的指向. 一.指向 window: 在全局中使用 this,它将会指向全 ...

  2. .Net Core Cookie-Based认证与授权

    .Net Core的其中一种认证与授权模式是基于Cookie的,首先我们先创建一个.Net Core MVC 项目: 然后增加对页面访问的权限控制,对要访问的页面Conytroller增加Author ...

  3. Mvc_扩展@html

    HtmlHelper的一个实例,它被定义在System.Web.Mvc名称空间下的WebViewPage类,即它对于所有MVC页面都可用)建立好扩展方法后: @Html.CreateGanderRad ...

  4. dpkg:错误:正在解析文件 '/var/lib/dpkg/updates/0014' 第 0 行附近:在字段名 #padding 中有换行符问题的解决方法

    解决方案如下: sudo rm /var/lib/dpkg/updates/* sudo apt-get update python@ubuntu:~/Desktop/_Welcome_.jpg.ex ...

  5. Centos7系统下修改主机名操作笔记

    习惯了在Centos6系统下修改主机名的操作,但是Centos7下修改主机名的操作却大不相同!操作笔记如下: 在CentOS中,有三种定义的主机名:静态的(static),瞬态的(transient) ...

  6. su: 无法设置用户ID: 资源暂时不可用

    登录非root用户,报错如下:[root@test ~]# su - appsu: 无法设置用户ID: 资源暂时不可用 或者用ssh 命令连接服务器之后,如果一段时间不操作,再次进入 Terminal ...

  7. Docker容器学习梳理 - 基础知识(1)

    Docker是PaaS 提供商 dotCloud 开源的一个基于 LXC 的高级容器引擎,源代码托管在 Github 上, 基于go语言并遵从Apache2.0协议开源.Docker是通过内核虚拟化技 ...

  8. 四则运算 C 语言

    #include<stdio.h>void main(){ char c; float x,y; int result; scanf("%c %f %f",&c ...

  9. individual project1 12061183

    1.项目预计用时      之前大二下学期的时候学过面向对象,当时老师叫我们写过一个统计目录下单词的程序,大致的思路是一样的.于是觉得这个程序并不难写.于是就在周末还很轻松地休息着不看程序,知道别的同 ...

  10. GIthub地址

    https://github.com/cuibaoxue/Text1