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

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

米奇妙妙题

题目的主要操作就是断掉一条边再连一条边权为0的边

我们考虑先不连那些后来加上的边权为0的边,先把所有的需要断的边都断掉,那么就形成了k+1个连通块

接下来的任务就是把所有的连通块连接在一起,可以发现,使得答案最大的连接法就是找出每个连通块中的直径后将所有块的直径依次相连,最后的答案就是所有块的直径的长度之和

现在我们就完成了问题的转化,那么现在我们要求的就是把原树分成k+1个连通块,使得所有块的直径之和最大

那么设f(k)表示将树分成k+1块的答案,设k=p时答案最大,那么显然有当k增大或减小时f(k)都会减小或不变

形式化的表达就是f(x+1)f(x)f(x)f(x1),也就是说对于相邻两点的斜率有xx1的斜率大于xx+1的斜率

证明:

对于第一种表示方法,可以理解为从f(p)的方案一步步改成了f(k),那么显然改的边越多,答案越劣

对于第二种表示方法,可以理解为从f(x1)的方案改成f(x),再从f(x)改成f(x+1),既然f表示的是最优解,那么肯定从f(x1)改成f(x)时会选择最优的方案,从f(x)改成f(x+1)只能选剩下的方案中最优的方案,也就是说肯定不会优于从f(x1)改成f(x)

那么我们就可以使用wqs二分,剩下的就是如何找在当前二分的mid斜率下的最大切点,也即最大的连通块数

因为我们计算的是每个连通块里的直径,也就是说对于这条直径中的点,它们的度数(指与直径中有多少个点相连)只有12两种情况,而不是直径中的点,其度数只能是0,但是因为我们在转移时肯定需要将点分成待与父亲相连的点不会再与父亲相连的点,所以稍微整理下这两种东西,我们就可以得出dp状态了:

dp[x][0]表示x不会再相连,当前x子树内的答案,此时x的度数为0/1/2,并且此时的x有两种情况,一种是其是直径中的点,一种是其不是直径中的点

dp[x][1]表示x还可以再相连,当前x子树内的答案,此时x的度数1,且x是直径中的点

dp[x][2]表示x不会再相连,但是x会和其两个儿子相连,也就是说x是其所处连通块中那条^状的直径的那个转折点,事实上dp[x][2]是属于dp[x][0]的,但是为了方便转移,单独提出来,且x是直径中的点

要注意的是因为我们现在在使用wqs二分,用mid表示当前二分的斜率,则有y=f(x0)k×x0,也就是说我们每分出一个连通块答案就需要k

那么转移有:

for(int i=1;i<=n;++i) dp[i][0]=dp[i][1]={0,0},dp[i][2]={-mid,1};//初始化
for(int i=head[x],y;i;i=tree[i].nxt) if((y=tree[i].v)!=fa){
	dfs(y,x);
	dp[x][2]=max(dp[x][2]+dp[y][0],dp[x][1]+dp[y][1]+make_pair(tree[i].val-mid,1)),
	dp[x][1]=max(dp[x][1]+dp[y][0],dp[x][0]+dp[y][1]+make_pair(1ll*tree[i].val,0)),
	dp[x][0]=dp[x][0]+dp[y][0];
}
dp[x][0]=max(dp[x][0],max(dp[x][1]+make_pair(-mid,1),dp[x][2]));

dppair类型,first是答案,second是满足当前答案的最大的连通块数量

这里就可以比较直观的感受定义了

因为dp[x][0]还包含了x不是直径中的点的情况,所以dp[i][0]的初始化是{0,0}而不是和dp[i][2]一样的{mid,1},包括大括号中转移部分,与dp[x][0]有关的部分转移不用像dp[i][2]一样要mid就是这个原因

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e5+5;
const ll INF=1e18;
int n,k;
int head[N],cntt;
struct node{
	int nxt,v,val;
}tree[N<<1];
void add(int u,int v,int val){
	tree[++cntt]={head[u],v,val},head[u]=cntt;
	tree[++cntt]={head[v],u,val},head[v]=cntt;
}
ll l,r,mid;
pair<ll,int> dp[N][3];
pair<ll,int> operator + (pair<ll,int> a,pair<ll,int> b){ return {a.first+b.first,a.second+b.second}; }
void dfs(int x,int fa){
	for(int i=head[x],y;i;i=tree[i].nxt) if((y=tree[i].v)!=fa){
		dfs(y,x);
		dp[x][2]=max(dp[x][2]+dp[y][0],dp[x][1]+dp[y][1]+make_pair(tree[i].val-mid,1)),dp[x][1]=max(dp[x][1]+dp[y][0],dp[x][0]+dp[y][1]+make_pair(1ll*tree[i].val,0)),dp[x][0]=dp[x][0]+dp[y][0];
	}
	dp[x][0]=max(dp[x][0],max(dp[x][1]+make_pair(-mid,1),dp[x][2]));
}
int get(){
	for(int i=1;i<=n;++i) dp[i][0]=dp[i][1]={0,0},dp[i][2]={-mid,1};
	dfs(1,0);
	return max(dp[1][0],max(dp[1][1],dp[1][2])).second;
}
int main(){
	scanf("%d%d",&n,&k),++k;
	for(int i=1,u,v,w;i<n;++i) scanf("%d%d%d",&u,&v,&w),add(u,v,w),r+=abs(w);
	l=-r;
	while(l<r){
		mid=l+r+1>>1;
		if(get()>=k) l=mid;
		else r=mid-1;
	}
	mid=l,get(),printf("%lld",l*k+dp[1][0].first);
	return 0;
}
posted @   LuoyuSitfitw  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示