对于k=0和k=1的点,可以直接求树的直径。
然后对于60分,有一个重要的转化:就是求在树中找出k+1条点不相交的链后的最大连续边权和。
这个DP就好。$O(nk^2)$
然后我们完全不可以想到,将best[k](选择k条链的答案)打表输出,更不可能然后作差分,发现得到的数组是递减的。
这说明:best[k]是一个上凸包。
于是我们可以二分一个斜率去切这个凸包(类似导数),根据切点横坐标与k的大小旋转直线(改变斜率)。
考虑给你一个直线斜率k,怎么找到它和凸包的切点。实际上就相当于将这个凸函数减去y=kx,再求凸包最高点。
感性理解一下,就是相当于在凸包下面画一条直线,然后旋转整个坐标系使这条直线就是x轴,然后正确性就比较显然了。
现在问题就是,如何找到最高点,这成了一个最优性问题,DP方程里可以去掉一维(已选链数不需要记录了)。
这样就可以通过了,复杂度$O(kn\log n)$。这又叫WQS二分。
https://www.luogu.org/problemnew/solution/P4383
https://blog.****.net/izumi_hanako/article/details/80071419
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=l; i<=r; i++)
typedef long long ll;
using namespace std; const int N=;
int n,k,u,v,w,cnt,to[N<<],nxt[N<<],val[N<<],h[N];
ll mid,tot;
void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
struct P{
ll x,y;
bool operator < (const P &b) const {return x==b.x? y>b.y : x<b.x;}
P operator + (const P &b) const {return (P){x+b.x,y+b.y};}
P operator + (int b) {return (P){x+b,y};}
}dp[][N];
P upd(P a){ return (P){a.x-mid,a.y+}; } void dfs(int u,int fa){
dp[][u]=max(dp[][u],(P){-mid,});
for (int i=h[u],v; i; i=nxt[i])
if ((v=to[i])!=fa){
dfs(v,u);
dp[][u]=max(dp[][u]+dp[][v],upd(dp[][u]+dp[][v]+val[i]));
dp[][u]=max(dp[][u]+dp[][v],dp[][u]+dp[][v]+val[i]);
dp[][u]=dp[][u]+dp[][v];
}
dp[][u]=max(dp[][u],max(upd(dp[][u]),dp[][u]));
} int main(){
freopen("lct.in","r",stdin);
freopen("lct.out","w",stdout);
scanf("%d%d",&n,&k); k++;
rep(i,,n) scanf("%d%d%d",&u,&v,&w),tot+=abs(w),add(u,v,w),add(v,u,w);
ll L=-tot,R=tot;
while (L<=R){
mid=(L+R)>>; memset(dp,,sizeof(dp)); dfs(,);
if (dp[][].y<=k) R=mid-; else L=mid+;
}
memset(dp,,sizeof(dp)); mid=L; dfs(,); printf("%lld\n",L*k+dp[][].x);
return ;
}