01分数规划

引入

因为在机房的后面看到DP中的第一个是分数规划,正好我没学过,所以在准备正式学习DP时,先学了分数规划。到百度上一搜,没有分数规划特别具体的概念,最多的就是01分数规划,所以就在前两天开始学习了01分数规划。

打的题不多,但是对01分数规划有一些感受,我觉得这个不应该归到DP的范畴内,更应该是数论,或者说是对一类问题的一个结论更加要形象化一些。

在这几天,引导我01分数规划做题的是这位大佬的一篇博客: https://www.cnblogs.com/Dawn-Star/p/9610410.html ,下面有部分的结论出自此处。


正文

一、 学习的内容和思路

首先在学习01分数规划之前要知道什么是01分数规划。

01分数规划是解决这样的一类问题,有一堆物品,每一个物品有一个收益 a[i],一个代价 b[1],我们要 求出一个方案 使选择的物品中性价比 ∑ a [ i ] / ∑ b [ i ] ∑a[i]/∑b[i] a[i]/b[i]最大或者是最小。

之后要明白求法:

  1. 我们给定两个数组,a[i]表示选取物品i的收益,b[i]表示选取物品i的代价。
  2. 定义一个数组x[i],如果选取物品i,x[i]=1否则x[i]=0,每个物品只有选和不选的两种方案(难道是这里感觉有点像是DP??)。
  3. 此时问题就变成了求出一个方案使得 R = ∑ ( a [ i ] ∗ x [ i ] ) / ∑ ( b [ i ] ∗ x [ i ] ) R=∑(a[i]*x[i])/∑(b[i]*x[i]) R=(a[i]x[i])/(b[i]x[i]),也就是选择物品的总收益/总代价(R)最大或者最小。

这个时候我们还可以化简上面的式子

R = ∑ ( a [ i ] ∗ x [ i ] ) / ∑ ( b [ i ] ∗ x [ i ] ) R=∑(a[i]*x[i])/∑(b[i]*x[i]) R=(a[i]x[i])/(b[i]x[i])
F ( R ) = ∑ ( a [ i ] ∗ x [ i ] ) − R ∗ ∑ ( b [ i ] ∗ x [ i ] ) F(R)=∑(a[i]*x[i])-R*∑(b[i]*x[i]) F(R)=(a[i]x[i])R(b[i]x[i])
F ( R ) = ∑ ( a [ i ] ∗ x [ i ] − R ∗ b [ i ] ∗ x [ i ] ) F(R)=∑(a[i]*x[i]-R*b[i]*x[i]) F(R)=(a[i]x[i]Rb[i]x[i])
F ( R ) = ∑ ( ( a [ i ] − R ∗ b [ i ] ) ∗ x [ i ] ) F(R)=∑((a[i]-R*b[i])*x[i]) F(R)=((a[i]Rb[i])x[i]) (最后就得到了这样的一个式子)

接下来,我们当然可以继续化简

换元法:令 h [ i ] = a [ i ] − R ∗ b [ i ] h[i]=a[i]-R*b[i] h[i]=a[i]Rb[i];
那么式子就在一次的变成了 F ( R ) = ∑ ( d [ i ] ∗ x [ i ] ) F(R)=∑(d[i]*x[i]) F(R)=(d[i]x[i])

还记得我们一开始的问题吗? 求的是R的最值。
这个时候就把一个求值问题,转化成了一个判定的问题。

当d[i]不断增大时,对应的R也是越来越大,说明R的求值具有单调性

是不是感觉这样的描述略微有一些熟悉??
最大最小,单调的……
这不就是二分的要求吗??
所以我们就可以通过二分R的值,进行R的求解。

所以这个问题就转化成了二分 + F ( R ) = ∑ ( ( a [ i ] − R ∗ b [ i ] ) ∗ x [ i ] ) +F(R)=∑((a[i]-R*b[i])*x[i]) +F(R)=((a[i]Rb[i])x[i])是否 > = 0 >=0 >=0的判定

最后,总结一下

  1. 解决这类问题最常用的就是实数二分,因为是比值,少不了有浮点数的参与;
  2. 注意,因为二分会可能不止一次,所以check()函数的初始化也是一个很大的问题。

之后分享一下自己做题的小技巧:

1.如果是在题中出现整数和实数,那么最优决策自然应该是实数二分
2.如果是要求最大公约数的的题目,c++中有内置函数,__gcd(a,b);【前面有两个杠杠】
3.对于数据比较小的数组赋值,建议直接for循环。


二、01分数规划解决的问题

  1. 一般的01分数规划
  2. 最优比率生成树
  3. 最优比率环

一般是解决这三类问题,反正都离不开是最优比率问题。


三、 一些例题

1.Dropping test

下面的图片摘自于 https://www.cnblogs.com/Dawn-Star/p/9610410.html
在这里插入图片描述

code

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

typedef long long ll;
const int nn=1010;

int n,k;
double sum=0;
struct number
{
	ll v,w;
	double z;
}d[nn];

inline ll read()
{
	ll x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

bool mycmp(number x,number y)
{
	return x.z>y.z;
}

bool check(double x)
{
	sum=0;
	for(int i=1;i<=n;++i)
		d[i].z=d[i].v-d[i].w*x;
	sort(d+1,d+1+n,mycmp);
	for(int i=1;i<=n-k;++i)
		sum+=d[i].z;
	return sum>=0;
}

int main()
{
	while(cin>>n>>k)
	{
		if(n==0&&k==0)	break;
		for(int i=1;i<=n;++i)	d[i].v=read();
		for(int i=1;i<=n;++i)	d[i].w=read();
		
		double l,r,mid;
		l=0,r=1000000000;
		while(l+1e-6<r)
		{
			mid=(l+r)/2.0;
			if(check(mid))	l=mid;
			else			r=mid;
		}
		mid=mid*100;
		int ans=(int)mid;
		if(mid-ans>=0.5)	ans=ans+1;
		printf("%d\n",ans); 
	}
	return 0;
}

2.1257 背包问题 V3

自己在做题的时候的一些思考:
错因

  1. 题目中说的是用约分后的分数表示
  2. 初始化的问题,s1,s2要清零;
  3. 该题并不是正数二分,应该是实数二分;

出现的思维问题:
Q:为什么该题是实数二分?
A:首先是因为可以看到题目中的答案要求是分数(分数是实数啊,而并非是整数,所以这道题一定不可以用整数二分来写);
其次是因为可以思考一下发现单位体积虽然可能正好是整数,但是同时也有可能是分数,那么如果是分数的话,整数二分是跑不出来的,但是实数二分可以,所以最优决策就应该是实数二分。

code

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

const int nn=50010;

int n,k;
int s1,s2;
struct number
{
	int w,v;/*x是weight,y是vaule*/
	double z;
}d[nn];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

bool mycmp(number x,number y)
{
	return x.z>y.z;
}

bool check(double x)
{
	s1=0,s2=0;
	for(int i=1;i<=n;++i)
		d[i].z=d[i].v-(d[i].w*x);
	sort(d+1,d+1+n,mycmp);
	double sum=0;
	for(int i=1;i<=k;++i)
	{
		s1+=d[i].v;
		s2+=d[i].w;
		sum+=d[i].z;
	}
	return sum>=0;
}

int main()
{
	n=read(); k=read();
	for(int i=1;i<=n;++i)	d[i].w=read(), d[i].v=read();/*1.数组存储变成了结构体*/
	double l=0,r=50010;
	double mid;/*2.精度的问题*/
	while(l+1e-6<r)
	{
		mid=(l+r)/2;
		if(check(mid))	l=mid;
		else			r=mid;
	}
	int h=__gcd(max(s1,s2),min(s1,s2));
	s1/=h; s2/=h;
	printf("%d/%d\n",s1,s2);
	return 0;
}

3.Desert King
大意:给定一张图,每条边有一个收益值和一个花费值, 求一个生成树,要求花费/收益最小,输出这个值
思路:现在的限制就有点复杂了,要求解必须是一棵生成 树。而且这道题目要求的花费/收益最小,当然你求收益/ 花费最大然后反过来也是可以的。如果求最小的,处理方法是也类似的,先求个d,然 后做一次最小生成树,显然得到的就是函数值。

错因
1.没有考虑c[i][i]的值,自己和自己之间肯定不可以建水道,但是自己在打的时候就完全没有考虑,也就是说,c[i][i]的值是0,那么这样子就会大致答案减少很多,这样就自然而然的wa掉了。
2.对于double类型的数组,不可以用memset整体赋值,否则的话会赋值非常小;

code

#include<iostream>
#include<algorithm>
#include <cctype>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstring>
#include <deque>
#include <functional>
#include <list>
#include <map>
#include <iomanip>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;

const int nn=1010;

int n;

struct village
{
	int x,y,z;
}e[nn];

int a[nn][nn];
double c[nn][nn],b[nn][nn];

double d[nn];
bool vis[nn];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

double s(int i,int j)
{
	return sqrt(1.0*((e[i].x-e[j].x)*(e[i].x-e[j].x)*1.0+(e[i].y-e[j].y)*(e[i].y-e[j].y)*1.0));
}

bool prim(double x)
{
	double ans=0;
	for(int i=1;i<=n;++i)
	{
		c[i][i]=0x3f3f3f;
		for(int j=i+1;j<=n;++j) 
			c[i][j]=c[j][i]=a[i][j]-b[i][j]*x;/*初始化的时候要考虑到自己不可以和自己建水道*/
	}
//	memset(d,0x3f3f3f,sizeof(d));/*对于double类型不可以用memset*/
	for(int i=1;i<=n;++i)	d[i]=10000010; 
	memset(vis,0,sizeof(vis));
	d[1]=0;
	for(int i=1;i<=n;++i)
	{
		int x=0;
		for(int j=1;j<=n;++j)
			if(!vis[j]&&(x==0||d[j]<d[x]))	x=j;
		vis[x]=1;
		for(int j=1;j<=n;++j)
			if(!vis[j])	d[j]=min(d[j],c[x][j]);
	}
	for(int i=2;i<=n;++i)
		ans+=d[i];
	return ans>=0;
}

void work()
{
	for(int i=1;i<=n;++i)/*第i个村庄*/
	{
		e[i].x=read();
		e[i].y=read();
		e[i].z=read();
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=i+1;j<=n;++j)
		{
			a[i][j]=a[j][i]=abs(e[i].z-e[j].z);/*价值*/
			b[i][j]=b[j][i]=s(i,j);/*长度*/

		}
	}	
	double l=0,r=10000010,mid;
	while(l+1e-9<r)
	{
		mid=(l+r)/2.0;
		if(prim(mid))	l=mid;
		else			r=mid; 
	}
	printf("%.3f\n",mid); 
}

int main()
{
	while(cin>>n&&n!=0)	work();
	return 0;
}

还有最后一道题是求最优比率环,但是我没有写,放一下题目链接,等回来有兴趣了再写。
4.Sightseeing Cows

推荐的讲01分数规划的博客:

  1. https://www.luogu.org/blog/yestoday/post-01-fen-shuo-gui-hua-yang-xie?tdsourcetag=s_pcqq_aiomsg
  2. https://www.cnblogs.com/Dawn-Star/p/9610410.html
  3. https://www.cnblogs.com/perseawe/archive/2012/05/03/01fsgh.html

The most beautiful words in the world not “I love you” not “together” But in my most vulnerable time You say, “I’m here”.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值