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

发布时间 2024-01-08 19:35:35作者: LuoyuSitfitw

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

米奇妙妙题

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

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

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

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

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

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

证明:

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

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

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

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

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

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

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

要注意的是因为我们现在在使用\(wqs\)二分,用\(mid\)表示当前二分的斜率,则有\(y=f(x_0)-k\times x_0\),也就是说我们每分出一个连通块答案就需要\(-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]));

\(dp\)\(pair\)类型,\(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;
}