hdu 6769 In Search of Gold(二分答案+树形dp)

传送门

题意:给一棵n个点的树,每条边的有两个边权a_{i}b_{i},现将某m条边的长度设为对应的a_{i},剩下n-m-1条边的长度设为b_{i}。问得到的树的直径最小为多少。2\le n\le 20000,0\le k\le n-1,k\le 20,1\le a_{i},b_{i}\le 10^{9}

题解:二分一个mid,check能否使这棵树的直径不大于mid。每次check时,由于k不大,可以用二维的树形dp来搞,定义f[i][p]表示p为根的子树内让i条边选a且保证子树内最长简单路径不超过mid时,子树内离p最远的点与p的距离最小值(有点绕,通过合理选择a来使这个距离尽量短)。最后如果f[k][1]\le mid,就说明当前mid可行。

从儿子v往当前点p转移的时候,一开始size[p]设为0,每转移一次就把这个size[v]加到size[p]里面,有点类似点分治时把两条链组合在一起的感觉。

P.S.这个二分有点妙,相当于二分了一个路径长度限制,在一定长度限制内去最短化最长路径。

细品......

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2e4+4;
int n,m;
ll f[21][N],t[21];
struct Edge {
	int v,nxt,a,b;
}e[N<<1];
int head[N],etot;
int siz[N];
ll l,r;
template <class T> inline void smax(T &x,T y) {
	x=x<y?y:x;
}
template <class T> inline void smin(T &x,T y) {
	x=x>y?y:x;
}
inline int read() {
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x*f;
}
inline void init() {
	etot=0;
	memset(head,-1,sizeof(head));
	memset(f,0x3f,sizeof(f));
}
inline void adde(int u,int v,int a,int b) {
	e[++etot].nxt=head[u],e[etot].v=v,e[etot].a=a,e[etot].b=b,head[u]=etot;
}
inline void dfs(int p,int fa,ll lim) {
	siz[p]=0;
	f[0][p]=0;
	for (int i=head[p];~i;i=e[i].nxt) {
		int v=e[i].v;
		if (v^fa) {
			dfs(v,p,lim);
			int pre=min(siz[p],m);
			int cur=min(siz[v],m);
			for (int j=0;j<=m;++j) t[j]=lim<<1;
			for (int j=0;j<=pre;++j)
				for (int k=0;k<=cur&&j+k<=m;++k) {
					if (f[j][p]+f[k][v]+e[i].a<=lim)
						smin(t[j+k+1],max(f[j][p],f[k][v]+e[i].a));
					if (f[j][p]+f[k][v]+e[i].b<=lim)
						smin(t[j+k],max(f[j][p],f[k][v]+e[i].b));
				}
			for (int j=0;j<=m;++j) f[j][p]=t[j];
			siz[p]+=siz[v];
		}
	}
	++siz[p];
}
inline bool ok(ll x) {
	dfs(1,0,x);
	return f[m][1]<=x;
}
inline ll find(ll le,ll ri) {
	ll ret;
	while (le<=ri) {
		ll mid=le+ri>>1;
		if (ok(mid)) ret=mid,ri=mid-1;
		else le=mid+1;
	}
	return ret;
}
int main() {
	int kase=read();
	while (kase--) {
		init();
		n=read(),m=read();
		l=r=0;
		for (register int i=1;i<n;++i) {
			int u=read(),v=read(),a=read(),b=read();
			adde(u,v,a,b);
			adde(v,u,a,b);
			r+=max(a,b);
		}
		printf("%lld\n",find(l,r));
	}
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值