【洛谷】P1642 规划 (01分数规划)

\(01\) 分数规划好题。

前置芝士:\(01\) 分数规划模型

给定整数 \(a_1\)\(a_2\),···,\(a_n\) 以及 \(b_1\)\(b_2\),···,\(b_n\),求一组解 \(x_i\) \((\) \(1 \leq i \leq n\)\(x_i=0\)\(x_i=1\) \()\) ,使得下式最大化:

\(\dfrac{ {\textstyle \sum_{i=1}^{a}} a_i*x_i}{ {\textstyle \sum_{i=1}^{b}} b_i*x_i}\)

自己的话(lyd大佬)的话来说,就是给定 \(n\) 对整数 \(a_i,b_i\) ,从中选出若干对,使选出的数对的 \(a\) 之和与 \(b\) 之和的商最大。

\(01\) 分数规划的基本做法:

取一个数值 \(mid\),然后判断是否存在一组可行解满足:

\(\dfrac{ {\textstyle \sum_{i=1}^{n}}a_i*x_i }{ {\textstyle \sum_{i=1}^{n}b_i*x_i} } \geq mid\)

直接求解比较困难,于是可以移项,得:

\({\textstyle \sum_{i=1}^{n} a_i*x_i} \geq {\textstyle \sum_{i=1}^{n}} b_i*x_i*mid\)

继续移项,得:

\({\textstyle \sum_{i=1}^{n}(a_i-b_i*mid)*x_i} \geq 0\)

而最终的答案就是 \(mid\) 的最大值。可以发现,这与二分答案非常相似,事实上,\(01\) 分数规划问题就是需要借助二分答案的方法进行求解。但是别忘了,可以使用二分答案的前提条件是满足单调性,而对于 \(01\) 分数规划来说,要求的答案 \(mid\) 显然存在单调性。 因为如果发现了一个可行的 \(mid\) ,那么肯定要往更大的方向去寻找是否存在更优的 \(mid\)

题意

给定一棵 \(n\) 个节点树,每个节点都有一个 \(a_i\)\(b_i\) 值,现在要删去 \(m\) 个点,在满足剩下的节点仍然能形成一颗树的前提下,求:

\(\dfrac{\sum a_i}{\sum b_i}\) 的最大值。

思路

初看本题,会发现题意和 \(01\) 分数规划模型非常像,但是题目还有一个额外要求,剩下的子节点仍然能形成一棵树。于是本题需要和树形 \(dp\) 结合起来做。

首先根据 \(01\) 分数规划的基本做法,先枚举可能的答案。根据题意,当所有的 \(a_i=10000\)\(b_I=1\) ,答案会取到上界 \(10000\) ,故最初的右端点 \(r=10000\) 。同时为了防止出错也可以将 \(r\) 值适当上调。

判断一个 \(mid\) 是否存在可行解时,可以将树上每个点的值都变成 \(a_i-mid*b_i\) 。这样就可以将判断转化为一个十分经典的树上背包问题:选择连续的 \(n-m\)个节点,判断其中的最大值是否 \(\geq 0\) 。如果存在,就令 \(l=mid\) ,反之,则令 \(r=mid\) 。最终得到的就是 \(mid\) 的最大值,也就是本题的答案。(二分答案的基本做法)

code:

#include<iostream>
using namespace std;
const int N=110;
const int INF=0x3f3f3f3f;
const double eps=1e-4;
struct edge{
	int v,w,nex;
}e[N<<1];
int size[N],h[N],idx,a[N],b[N],n,m;
double d[N],f[N][N];
void add(int u,int v)
{
	e[++idx].v=v;
	e[idx].nex=h[u];
	h[u]=idx;
}
void dfs(int u,int fa)
{
	size[u]=1;
	f[u][0]=0;
	for(int i=h[u];i;i=e[i].nex)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);
		size[u]+=size[v];
		for(int j=min(size[u],m);j>=0;j--)
		    for(int k=0;k<=min(j,size[v]);k++)
		        f[u][j]=max(f[u][j],f[v][k]+f[u][j-k]);
	}
	for(int i=min(size[u],m);i>=1;i--) f[u][i]=f[u][i-1]+d[u];
}
bool check(double mid)
{
	for(int i=1;i<=n;i++)
	    for(int j=0;j<=m;j++)
	        f[i][j]=-INF;
	for(int i=1;i<=n;i++) d[i]=a[i]*1.0-mid*(b[i]*1.0);
	dfs(1,0);
	for(int i=1;i<=n;i++)
	    if(f[i][m]>-eps) return true;
	return false;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	for(int u,v,i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	m=n-m;
	double l=0,r=10010;
	while(r-l>eps)
	{
		double mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	printf("%.1lf\n",r);
	return 0;
}
posted @ 2021-06-04 11:30  曙诚  阅读(195)  评论(0)    收藏  举报