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