[八省联考2018]林克卡特树

林克卡特树

题解

挺简单的一道题。

原题断 k k k条边连 k k k条边权为 0 0 0的边相当于寻去 k + 1 k+1 k+1条不相交链出来,将它们连上得到的结果。
所以我们要从原树中选取 k + 1 k+1 k+1条链出来,使得它们的权值和最大。

我们发现恰好 k + 1 k+1 k+1条链这个限制是比较难限制的,考虑通过凸优化二分去进行维护。
由于链选取的条数关于贡献的函数一定是一个凸包,我们可以去二分通过选取 k k k条链的点的切线的斜率,来进行判断。
我们将每条路径的贡献减去当前的二分值,再直接用二分去选取不限量的路径,使得权值最大。
dp出来的选取链数大于 k k k,则说明我们当前二分的值大了,否则就说明小了,只有当选取刚好 k k k条路径时才能说明我们二分的值是恰当的。

关于这个dp,就是个简单的树dp,在树上找链,相信大家都会。

总时间复杂度 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn)

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
#define MAXN 300005
#define lowbit(x) (x&-x)
#define reg register
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
const LL INF=0x7f7f7f7f7f7f;
const int mo=1e9+7;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,k,head[MAXN],tot;LL dif;
struct edge{int to,nxt;LL paid;}e[MAXN<<1];
void addEdge(int u,int v,LL w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
struct ming{
	LL sum;int num;
	friend ming operator + (const ming &x,const ming &y){
		return (ming){x.sum+y.sum,x.num+y.num};
	}
	friend bool operator < (const ming &x,const ming &y){
		if(x.sum==y.sum)return x.num<y.num;
		return x.sum<y.sum;
	}
}dp[MAXN][3];
ming Max(ming x,ming y){return x<y?y:x;}
void dosaka(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;dosaka(v,u);
		dp[u][2]=Max(dp[u][2]+dp[v][0],dp[u][1]+dp[v][1]+(ming){e[i].paid-dif,1});
		dp[u][1]=Max(dp[u][1]+dp[v][0],dp[u][0]+dp[v][1]+(ming){e[i].paid,0});
		dp[u][0]=dp[u][0]+dp[v][0];
	}
	dp[u][0]=Max(dp[u][0],Max(dp[u][2],dp[u][1]+(ming){-dif,1}));
}
LL sakura(const LL mid){	
	for(int i=1;i<=n;i++)
		dp[i][0]=dp[i][1]=(ming){0,0},dp[i][2]=(ming){-mid,1};
	dif=mid;dosaka(1,0);return dp[1][0].num;
}
signed main(){
	read(n);read(k);LL sum=0;
	for(int i=1;i<n;i++){
		int u,v;LL w;read(u);read(v);read(w);
		addEdge(u,v,w);addEdge(v,u,w);sum+=Fabs(w);
	}
	LL l=-sum,r=sum;
	while(l<r){
		LL mid=l+r+1>>1;
		if(sakura(mid)>=k+1)l=mid;
		else r=mid-1;
	}
	LL tmp=sakura(l);printf("%lld",dp[1][0].sum+1ll*(k+1)*l);
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值