【OI】[poj2728&3621] 分数规划问题入门

【OI】[poj2728&3621] 分数规划问题入门

首先来讲讲啥是分数规划问题。

对于一个集合 \mathbf{A} ,每个元素都有两个键值(x,y),给定某种必须遵守的组合关系,求在这些元素中选一些出来记作新集合 \mathbf{D} 并且使得 \frac{\sum_{i \in \mathbf{D}}x_i}{\sum_{i \in \mathbf{D}}y_i} 的值最大或者最小。

对于这种题我们的一种普通套路是,对于取值范围 [L,R] ,二分一个答案 Ans 作为当前可能答案,这里我们用求最大值作为例子,那么我们可以知道对于 Ans 的走向分为下面两种情况:

if \quad \exists \mathbf{D} \quad \frac{\sum_{i \in \mathbf{D}}x_i}{\sum_{i \in \mathbf{D}}y_i} > Ans ,则最终答案一定大于当前 Ans ,因此 L=Ans

if \quad \forall \mathbf{D} \quad \frac{\sum_{i \in \mathbf{D}}x_i}{\sum_{i \in \mathbf{D}}y_i} < Ans ,则肯定不存在一个可行的方案使得答案达到当前 Ans ,因此 R=Ans

好下面我们来看看题:

传送门:[poj2728]Desert King

题意:给n个 (n\leq10000) 位置的二维坐标 (x,y) 和海拔 h ,定义两点通道长度为二维坐标的欧几里得距离,两点修通道的花费是两点的海拔之差,要求修n-1条水管形成一个生成树,使得通道总花费与通道总长度的比率最小。

这道题是经典的最优比率生成树问题,照上面的公式我们可以在二分一个答案 Ans 后构建一个新的图使得每条边长度为  \Delta h_{ij}-dis_{ij} * Ans ,然后在上面跑最小生成树,如果得到的生成树大小<0,则说明对于 \exists \mathbf{D} \quad \frac{\sum_{i,j \in \mathbf{D}}\Delta h_{ij}}{\sum_{i ,j\in \mathbf{D}}dis_{ij}} < Ans ,则令 L = Ans ,反之则说明对于 \forall \mathbf{D} \quad \frac{\sum_{i,j \in \mathbf{D}}\Delta h_{ij}}{\sum_{i ,j\in \mathbf{D}}dis_{ij}} > Ans ,则令 R=Ans ,因为这是一个稠密图,所以我们用prim算法,时间复杂度为: \Theta(n^2 logn)


下一个问题:

传送门:[poj3621]Sightseeing Cows

题意:给n个点 (n\leq 1000) ,m条边 (m \leq 5000) 组成的有向图,点和边都有一个权值,求一个图上的环使得环上的点权之和与环上边权之和的比率最大。

这道题我们也可以用分数规划的思路来做,对于一个环 S ,我们要做的是尽量让 \frac{\sum_{i \in \mathbf{S}}vertex_i}{\sum_{i \in \mathbf{S}}edge_i} 最大,因此我们可以用类似的手法,若找到 \exists S \quad \frac{\sum_{i \in \mathbf{S}}vertex_i}{\sum_{i \in \mathbf{S}}edge_i} > Ans 则让 L = Ans ,否则就让 R = Ans ,转化一下式子就变成了 \exists S \quad \sum_{i \in \mathbf{S}}vertex_i-edge_i \times Ans > 0 ,而我们的任务就是寻找是否有一个符合条件的环 S ,其实也就是判断有无负环,这时候我们就理所当然地想到了我们的SPFA酱,我们可以令每个边权为 vertex_i-edge_i \times Ans ,其中 vertex_iedge_i 的起点,因为是在环上所以保证每个点和边都能被算进去,这是一个等价变换,所以我们可以在这张图上跑SPFA判断有无负环并二分答案即可。





T1代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 1005
#define inf 10000001
#define fo(i, a, b) for(int i = (a); i <= (b); i++)
int x[N], y[N], c[N], n, tot = 0, fa[N], cnt, fx, fy;
bool vis[N];
double mid, l, r, sum, dis[N];
struct edge{
	double dis;
	int w;
	friend inline bool operator < (edge x, edge y)
	{
		return (x.w - mid * x.dis) < (y.w - mid * y.dis);
	}
	inline double val ()
	{
		return w - mid * dis;
	}
}e[N][N];
inline int sqr (int x) {return x * x;}
inline int abs (int x) {return (x < 0) ? -x : x;}
inline double prim ()
{
	double ans = 0;
	fo (i, 1, n)
		dis[i] = inf;
	fo (i, 1, n)
		vis[i] = 0;
	vis[1] = 1; dis[1] = 0;
	fo (i, 2, n)
		dis[i] = e[1][i].val();
	int mini;
	fo (k, 2, n)
	{
		double minx = inf;
		fo (i, 1, n)
			if (!vis[i] && minx > dis[i])
			{
				minx = dis[i];
				mini = i;
			}
		ans += minx;
		vis[mini] = 1;
		fo (i, 1, n)
			if (!vis[i] && dis[i] > e[mini][i].val())
				dis[i] = e[mini][i].val();
	}
	return ans;
}
int main()
{
//	freopen("miao.txt", "r", stdin);
	while (~scanf("%d", &n), n)
	{
		fo (i, 1, n)
			scanf("%d %d %d", &x[i], &y[i], &c[i]);
		tot = 0;
		fo (i, 1, n)
			fo (j, i + 1, n)
			{
				e[i][j].dis = std::sqrt(sqr(y[i] - y[j]) + sqr(x[i] - x[j]));
				e[i][j].w = abs(c[i] - c[j]);
				e[j][i] = e[i][j];
			}
		l = 0; r = inf;
		while (l + 0.0001 < r)
		{
			mid = (l + r) / 2;
			sum = prim();
			if (sum > 0)
				l = mid;
			else
				r = mid;
		}
		printf("%.3f\n", l);
	}
	return 0;
}



T2代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 1005
#define M 5005
#define inf 10000001
#define fo(i, a, b) for(int i = (a); i <= (b); i++)
int n, m, t[N], x, y, hh, tt;
int head[M], to[M], next[M], tot = 0, val[M], from[M];
bool vis[N];
double l, r, mid, dis[N], nowv[M];
inline void read()
{
	int u, v, w;
	scanf("%d %d %d", &u, &v, &w);
	to[++tot] = v;
	from[tot] = u;
	val[tot] = w;
	next[tot] = head[u];
	head[u] = tot;
}
struct que{
	int cnt, num;
}q[N];
inline bool spfa ()
{
	fo (i, 1, n)
	{
		dis[i] = inf;
		vis[i] = 0;
	}
	fo (i, 1, tot)
		nowv[i] = mid * val[i] - t[from[i]];
	hh = tt = 0;
	q[0].cnt = 0;
	q[0].num = 1;
	dis[1] = 0;
	vis[1] = 1;
	int now, nxt;
	while (hh != tt + 1)
	{
		now = q[hh].num;
		for (int i = head[now]; i; i = next[i])
		{
			nxt = to[i];
			if (dis[now] + nowv[i] < dis[nxt])
			{
				dis[nxt] = dis[now] + nowv[i];
				if (!vis[nxt])
				{
					vis[nxt] = 1;
					tt++;
					tt = (tt == N) ? 0 : tt;
					q[tt].num = nxt;
					q[tt].cnt = q[hh].cnt + 1;
					if (q[tt].cnt > n) return 0;
				}
			}
		}
		vis[now] = 0;
		hh++;
		hh = (hh == N) ? 0 : hh;
	}
	return 1;
}
int main()
{
	//freopen("miao.txt", "r", stdin);
	scanf("%d %d", &n, &m);
	fo (i, 1, n)
		scanf("%d", &t[i]);
	fo (i, 1, m)
		read();
	l = 0;
	r = inf;
	while (l + 0.001 < r)
	{
		mid = (l + r) / 2;
		if (spfa())
			r = mid;
		else
			l = mid;
	}
	printf("%.2f", l);
	return 0;
}

本文为蒟蒻所作,若需转载请注明出处。

由于本蒟蒻水平有限,若有语言欠妥之处请在评论区指出,谢谢。

编辑于 2018-03-31