洛谷P4383 林克卡特树
题意:树上最长不相交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 林克卡特树的更多相关文章
- P4383 [八省联考2018]林克卡特树 树形dp Wqs二分
LINK:林克卡特树 作为树形dp 这道题已经属于不容易的级别了. 套上了Wqs二分 (反而更简单了 大雾 容易想到还是对树进行联通情况的dp 然后最后结果总和为各个联通块内的直径. \(f_{i,j ...
- 【BZOJ5252】林克卡特树(动态规划,凸优化)
[BZOJ5252]林克卡特树(动态规划,凸优化) 题面 BZOJ(交不了) 洛谷 题解 这个东西显然是随着断开的越来越多,收益增长速度渐渐放慢. 所以可以凸优化. 考虑一个和\(k\)相关的\(dp ...
- LuoguP4383 [八省联考2018]林克卡特树lct
LuoguP4383 [八省联考2018]林克卡特树lct https://www.luogu.org/problemnew/show/P4383 分析: 题意等价于选择\(K\)条点不相交的链,使得 ...
- 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)
To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...
- [八省联考2018]林克卡特树lct——WQS二分
[八省联考2018]林克卡特树lct 一看这种题就不是lct... 除了直径好拿分,别的都难做. 所以必须转化 突破口在于:连“0”边 对于k=0,我们求直径 k=1,对于(p,q)一定是从p出发,走 ...
- [BZOJ 5252][LOJ 2478][九省联考2018] 林克卡特树
[BZOJ 5252][LOJ 2478][九省联考2018] 林克卡特树 题意 给定一个 \(n\) 个点边带权的无根树, 要求切断其中恰好 \(k\) 条边再连 \(k\) 条边权为 \(0\) ...
- 【BZOJ2830/洛谷3830】随机树(动态规划)
[BZOJ2830/洛谷3830]随机树(动态规划) 题面 洛谷 题解 先考虑第一问. 第一问的答案显然就是所有情况下所有点的深度的平均数. 考虑新加入的两个点,一定会删去某个叶子,然后新加入两个深度 ...
- luoguP4383 [八省联考2018]林克卡特树(树上dp,wqs二分)
luoguP4383 [八省联考2018]林克卡特树(树上dp,wqs二分) Luogu 题解时间 $ k $ 条边权为 $ 0 $ 的边. 是的,边权为零. 转化成选正好 $ k+1 $ 条链. $ ...
- 洛谷P3655 差分数组 树状数组
题目链接:https://www.luogu.org/problemnew/show/P3655 不一定对,仅供参考,不喜勿喷,不喜勿喷. 先copy洛谷P3368 [模板]树状数组 2 题解里面一位 ...
随机推荐
- Docker系列学习
一.Docker入门 1.Docker概述与安装 2.Docker镜像管理 3.Docker容器管理 4.Docker数据管理 5.Docker网络配置 6.Docker图形化管理 7.Docker监 ...
- KETTLE集群搭建
KETTLE集群搭建 说明: 本文档基于kettle5.4 一.集群的原理与优缺点 1.1集群的原理 Kettle集群是由一个主carte服务器和多个从carte服务器组成的,类似于master-sl ...
- Python 可调用对象
除了用户定义的函数,调用运算符(即 ())还可以应用到其他对象上.如果想判断对象能否调用,可以使用内置的 callable() 函数.Python 数据模型文档列出了 7 种可调用对象.(1)用户定义 ...
- B. Diagonal Walking v.2
链接 [https://i.cnblogs.com/EditPosts.aspx?opt=1] 题意 二维平面从原点出发k步,要到达的点(x,y),每个位置可以往8个方位移动,问到达目的地最多可以走多 ...
- Genymotion安装总结
周末的时候为了测试论文中的Almond虚拟助手软件,所以要去Google Play上去下载. 但是我的两个安卓模拟器:夜神和海马玩模拟器的安卓版本太低了,导致无法使用 谷歌服务,所以连商店都进不去. ...
- HDU 2033 人见人爱A+B
http://acm.hdu.edu.cn/showproblem.php?pid=2033 Problem Description HDOJ上面已经有10来道A+B的题目了,相信这些题目曾经是大家的 ...
- Node 开启
cmd //进入命令行 D: //指定磁盘 cd 文件路径 //指定路径 node 文件名.js //执行文件 增补: Node执行js文件自动嵌套 (functio ...
- spark中saveAsTextFile的错误
写了很简单的一段spark代码,将结果保存为windows本地文件,执行之后总是报错NullPointerException 查询之后 发现是本地缺少hadoop需要的一个文件所致 如果本地已经安装了 ...
- 关于EXCEL if、countif 在查找数据的用法
最近被其他部门的同事教导使用excel.突然觉得以前用代码切来切去的东西,和频繁比对的数据原来是用excel就能那么方便的算出,瞬间感觉打开了新世界的大门. 先说if和countif结合使用,来判断一 ...
- Omni(USDT)钱包安装(ubuntu)
一.下载Omni Layer钱包 wget https://bintray.com/artifact/download/omni/OmniBinaries/omnicore-0.3.0-x86_64- ...