qwq

安利一个凸优化讲的比较好的博客

https://www.cnblogs.com/Gloid/p/9433783.html

但是他的暴力部分略微有点问题

qwq

我还是详细的讲一下这个题+这个知识点吧。

还是先从题目入手。

首先我们分析题目。

因为题目要删除\(k\)条边,然后再新建\(k\)条边,求两点的路径和。

那我们不妨这么考虑,对于新连接一条边,相当于链接了原树上的两条链,且链不存在交点。

那我们新建\(k\)条边,就相当于把原树上没有交的\(k+1\)条链连接起来。

既然要求权值最大。

那我们就可以直接把题目转换成求树上选出点不相交的\(k+1\)条链的最大收益。(每个点都是一条链)。

qwq

首先考虑应该怎么

暴力\(dp\)

由于对于\(x\)这颗子树,要分情况讨论他属于一条链的端点,中间点,还是不属于链。

所以我们定义状态\(dp[i][j][0/1/2]\)表示\(i\)的子树里,已经选了\(j\)条链,其中\(i\)这个点不属于链,属于一个链的端点,属于一个链的中心点的最大收益。

首先,因为负权,所以要把所有的\(dp\)弄成初始值是\(-inf\)的,其中\(dp[i][0][0]\)为0。

然后我们考虑应该怎么转移。

对于一个点来说,我们先枚举他的儿子,然后枚举他子树内的链数\(j\),枚举分配给他的儿子的链数\(z\)

那么对于不同的\(0/1/2\)我们需要分情况讨论。

对于\(0\)来说,他可以从儿子的任意一个状态转移过来。

对于\(1\)来说,首先他可以由当前状态的\(1\)+儿子的\(0/1/2\)中最大的那个转移,表示不与当前的儿子构成链。

也可以从当前状态的0+儿子的1+\(val[i]\),表示从当前儿子上来一条链。

另外的一种情况就是只选与当前儿子相连的边。

对于2来说,其实也是和1同理。

void solve(int x,int fa)
{
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if(p==fa) continue;
solve(p,x);
for (int j=min(k,size[x]);j>=1;j--)
{
for (int z=0;z<=min(j,size[p]);z++)
{
dp[x][j][2]=max(dp[x][j][2],dp[x][j-z][2]+max(dp[p][z][0],max(dp[p][z][1],dp[p][z][2])));
dp[x][j][2]=max(dp[x][j][2],dp[x][j-z][1]+dp[p][z+1][1]+val[i]);
if (j>z) dp[x][j][2]=max(dp[x][j][2],dp[x][j-z][1]+val[i]+dp[p][z][0]);
//本身就有1一条链
dp[x][j][1]=max(dp[x][j][1],dp[x][j-z][1]+max(dp[p][z][0],max(dp[p][z][1],dp[p][z][2])));
//和当前构成一条链
dp[x][j][1]=max(dp[x][j][1],dp[x][j-z][0]+dp[p][z][1]+val[i]);
if (j>z) dp[x][j][1]=max(dp[x][j][1],dp[x][j-z-1][0]+dp[p][z][0]+val[i]);
dp[x][j][0]=max(dp[x][j][0],dp[x][j-z][0]+max(dp[p][z][0],max(dp[p][z][1],dp[p][z][2])));
}
}
}
}

这里有两个需要注意的问题,首先是\(j\)要倒着枚举,因为一个链只能被选一次(防止出现旧自己更新新自己的情况)

其次\(z\)要循环到0(只是用来针对只算一条边的情况。)

然后通过以上的过程,我们就能轻松愉悦的通过\(O(nk)\)的60分了。

那么应该怎么优化这个过程呢。

凸优化

首先,我们通过做差分,即\(ans[i][j]-ans[i][j-1]\),\(ans[i][j]\)表示\(n=i,k=j\)时候的答案,发现斜率是逐渐降低的,那我们不难发现在\(i\)一定的情况下,其实\(ans\)是关于\(j\)的上凸函数。(并且一定要满足斜率单调!!!!!!)、

那我们实际上就是要求出来在\(j=k\)的时候的那个对应的凸包的点是多少。

根据斜率单调

我们可以直接二分一个\(mid\),然后让所有的边权都加上\(mid\),然后不限制选的链的条数,求最大收益和链数,这个复杂度是\(O(n)\)的。

因为我们发现,当\(mid= inf\)的时候,一定是选\(n\)条,当\(inf=-mid\)的时候,是选0条,那么因为斜率单调(所以选择的条数一定是随着mid单调变大的),所以我们一定能通过改变这个\(mid\),存在某一个\(mid\)满足恰好选择\(k\)条,那么正好符合题目要求。

另外一种理解方式。

qwq这个理解方式,每次要减去\(mid\),其实本质是一样的。

我们相当于对于每个点求出他的正上方所对应的凸包的点是啥,那么我们通过\(erf\)这个东西,相当于把原图的上的每个点\(x\)向下移动\(mid*x\),那么我们可以发现,随着\(mid\)的变大,每次随便选的取到的收益的最高的地方,是单调右移的。我们只需要记录一下选的最大收益和链数,然后找到链数等于\(k\)所对应的\(mid\),计算一遍贡献就行。

至于如何做不限制条数的收益,和\(nk\)类似的\(dp\)

感觉这个东西要感性+理性啊

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long
#define int long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 3e5+1e2;
const int maxm = 2*maxn;
const int inf = 1e12;
int point[maxn],nxt[maxm],to[maxm],val[maxm];
int n,m,cnt;
int l,r,ans,k;
struct ymh{
int val,num;
ymh operator + (ymh b)
{
return (ymh){val+b.val,num+b.num};
}
};
ymh max(ymh a,ymh b)
{
if(a.val==b.val)
{
if(a.num<b.num) return a;
else return b;
}
else
{
if(a.val<b.val) return b;
else return a;
}
}
void addedge(int x,int y,int w)
{
nxt[++cnt]=point[x];
to[cnt]=y;
val[cnt]=w;
point[x]=cnt;
}
ymh dp[maxn][3];
void dfs(int x,int fa,int lim)
{
dp[x][0]=(ymh){0,0};
dp[x][1]=(ymh){-inf,-inf};//不存在这种情况
dp[x][2]=(ymh){lim,1};//可以将一个点看成一条链
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
dfs(p,x,lim);
ymh now = max(dp[p][0],max(dp[p][1],dp[p][2]));
dp[x][2]=max(dp[x][2],dp[x][2]+now); //本身就有链
dp[x][2]=max(dp[x][2],dp[x][1]+dp[p][0]+(ymh){val[i],0}); //新加一条边
dp[x][2]=max(dp[x][2],dp[x][1]+dp[p][1]+(ymh){val[i]-lim,-1});//用一条边合并两个链(链数会减一)
dp[x][1]=max(dp[x][1],dp[x][1]+now);
dp[x][1]=max(dp[x][1],dp[x][0]+dp[p][1]+(ymh){val[i],0});//沿着之前的链
dp[x][1]=max(dp[x][1],dp[x][0]+dp[p][0]+(ymh){val[i]+lim,1});//新增一个链
dp[x][0]=max(dp[x][0],dp[x][0]+now);
}
}
signed main()
{
n=read(),k=read();
for (int i=1;i<n;i++)
{
int x=read(),y=read(),w=read();
addedge(x,y,w);
addedge(y,x,w);
r+=w;
}
k++;
r=inf,l=-inf;
while(l<=r)
{
int mid = l+r >> 1;
memset(dp,0,sizeof(dp));
dfs(1,0,mid);
ymh now = max(dp[1][0],max(dp[1][1],dp[1][2]));
if (now.num<=k) ans=mid,l=mid+1;
else r=mid-1;
}
memset(dp,0,sizeof(dp));
dfs(1,0,ans);
ymh now = max(dp[1][0],max(dp[1][1],dp[1][2]));
cout<<now.val-k*ans;
return 0;
}

dp凸优化/wqs二分学习笔记(洛谷4383 [八省联考2018]林克卡特树lct)的更多相关文章

  1. 洛谷 4383 [八省联考2018]林克卡特树lct——树形DP+带权二分

    题目:https://www.luogu.org/problemnew/show/P4383 关于带权二分:https://www.cnblogs.com/flashhu/p/9480669.html ...

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

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

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

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

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

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

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

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

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

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

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

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

  8. BZOJ5252 八省联考2018林克卡特树(动态规划+wqs二分)

    假设已经linkcut完了树,答案显然是树的直径.那么考虑这条直径在原树中是怎样的.容易想到其是由原树中恰好k+1条点不相交的链(包括单个点)拼接而成的.因为这样的链显然可以通过linkcut拼接起来 ...

  9. luogu4383 [八省联考2018]林克卡特树(带权二分+dp)

    link 题目大意:给定你 n 个点的一棵树 (边有边权,边权有正负) 你需要移除 k 条边,并连接 k 条权值为 0 的边,使得连接之后树的直径最大 题解: 根据 [POI2015]MOD 那道题, ...

随机推荐

  1. python3使用pycuda执行简单GPU计算任务

    技术背景 GPU的加速技术在深度学习.量子计算领域都已经被广泛的应用.其适用的计算模型是小内存的密集型计算场景,如果计算的模型内存较大,则需要使用到共享内存,这会直接导致巨大的数据交互的运算量,通信开 ...

  2. java js转码解码

    摘自网友:https://blog.csdn.net/sgear/article/details/1509400?utm_medium=distribute.pc_relevant.none-task ...

  3. new Vue({ render: h => h(App), }).$mount('#app')

    这里创建的vue实例没有el属性,而是在实例后面添加了一个$mount('#app')方法. $mount('#app') :手动挂载到id为app的dom中的意思 当Vue实例没有el属性时,则该实 ...

  4. 关于Ubuntu18.04上Python版本管理

    时间: 2019-11-11 整理: pangyuaner 标题:树梅派上多版本python及pip安装使用管理指南 地址:https://blog.csdn.net/zbgjhy88/article ...

  5. Focal Loss(RetinaNet)笔记 一种减小类别不平衡影响的方法

    Paper: https://arxiv.org/abs/1708.02002 还参考了:https://www.jianshu.com/p/8e501a159b28 其中p是预测属于某类的概率.

  6. css之px、em、rem

    rem是css3新定义的设置字体大小属性,常用的两种字体大小设置有下面2种:1. px为单位2.em为单位(百分比用法跟em类似) PX为单位 在Web页面初期制作中,我们都是使用"px&q ...

  7. 20210821 打表,蛇,购物,ants

    考场 T1 没看懂 T4 一眼回滚莫队,但忘记怎么写了,小慌 模拟 T1 题意的时候教练让 zsy 澄清了一下,确定了我不会做... T2 一看就是毒瘤题,T3 感觉比较可做 T4 确定了回滚的细节, ...

  8. C语言实现线程池功能

    1. 线程池基本原理 2. 线程池C语言实现 2.1 线程池的数据结构 #include <stdio.h> #include <pthread.h> #include < ...

  9. 远程线程注入DLL突破session 0 隔离

    远程线程注入DLL突破session 0 隔离 0x00 前言 补充上篇的远程线程注入,突破系统SESSION 0 隔离,向系统服务进程中注入DLL. 0x01 介绍 通过CreateRemoteTh ...

  10. 迷你DVD

    public class DVD { private int ID;//id private String status;//状态 private String name;//名称 private S ...