hdu6769 In Search of Gold 2020杭电hdu多校第2场

http://acm.hdu.edu.cn/showproblem.php?pid=6769

正常的求直径的树形dp一般是记录一个最长链和一个次长链,然后第二遍换根dp再记录一个从父节点转移过来的最长链,然后求出这个直径。然而这题需要加一维j表示当前子树已经使用了j个a边后的最长链和次长链,这就很难搞了,因为无法保证最长链最小的同时保证最长链+次长链最小,然后思考了2小时人生。

赛后题解理解不能,还是群巨牛逼,一问就懂。群巨补题地址 http://www.koule2333.top:3000/s/H1Lv7U1A8

这题并不直接维护直径,不求出直径,而是二分直径的长度mid后,维护在直径不超过mid前提下,每个子树中所有点到子树根节点的最长距离的最小值。

那么我们对子树根节点u,它的一棵子树v进行合并,一开始u已经合并了部分子树,现在把子树v也合并到u

首先定一个临时变量数组tmp,表示合并之后的最长距离的最小值,然后枚举连通块u的已经用的过的a边数量j,再枚举子树v中已经用过的a边数量k,那么如果j+k+1<=m,就可以更新tmp[j+k+1],此时的最大值就是max(dp[u][j],dp[v][k]+a),如果u-v这条边不用,那就是upd(tmp[j+k],max(dp[v][k]+b,dp[u][k])),这样我们在转移时会丢掉一些使得直径大于mid的情况,最后留到根节点的始终是保证直径<=mid的,那么如果dp[1][m]<=inf,则存在整体直径<=mid的情况。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxl=2e4+10;
const ll inf=1ll<<60;

int n,m;
ll l,r,mid,ans;
int sz[maxl];
ll tmp[21];
ll dp[maxl][21];
struct ed{int to,a,b;};
vector<ed> e[maxl];

inline void prework()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		e[i].clear();
	int u,v,a,b;l=1;r=0;
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d%d%d",&u,&v,&a,&b);
		e[u].push_back(ed{v,a,b});
		e[v].push_back(ed{u,a,b});
		r+=max(a,b);
	}
}

inline void upd(ll &x,ll y)
{
	if(x>y) x=y;
}

inline bool dfs(int u,int fa)
{
	for(int i=0;i<=m;i++) 
		dp[u][i]=inf;
	dp[u][0]=0;
	int v,up;sz[u]=0;
	for(ed ee:e[u])
	{
		v=ee.to;
		if(v==fa) continue;
		dfs(v,u);up=min(m,sz[u]+sz[v]+1);
		for(int j=0;j<=m;j++)
			tmp[j]=inf;
		for(int j=0;j<=sz[u];j++)
			for(int k=0;k<=sz[v];k++)
			{
				if(dp[u][j]+dp[v][k]+ee.a<=mid && j+k+1<=m)
					upd(tmp[j+k+1],max(dp[u][j],dp[v][k]+ee.a));
				if(dp[u][j]+dp[v][k]+ee.b<=mid && j+k<=m)
					upd(tmp[j+k],max(dp[u][j],dp[v][k]+ee.b));
			}
		for(int j=0;j<=m;j++)
			dp[u][j]=tmp[j];
		sz[u]=up;
	}
}

inline bool jug(){dfs(1,0);return dp[1][m]<=mid;}

inline void mainwork()
{
	while(l+1<r)
	{
		mid=(l+r)>>1;
		if(jug())
			r=mid;
		else
			l=mid;
	}
	mid=l;
	if(jug())
		ans=l;
	else
		ans=l+1;
}

inline void print()
{
	printf("%lld\n",ans);
}

int main()
{
	int t;
	scanf("%d",&t);
	for(int i=1;i<=t;i++)
	{
		prework();
		mainwork();
		print();
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/liufengwei1/article/details/107551702