洛谷 P1730 最小密度路径


Description

给出一张有 \(N\) 个点 \(M\) 条边的加权有向无环图,接下来有 \(Q\) 个询问,每个询问包括 \(2\) 个节点 \(X\)\(Y\),要求算出从 \(X\)\(Y\) 的一条路径,使得密度最小(密度的定义为,路径上边的权值和除以边的数量),若无法到达输出 "\(OMG!\)"。

Constraints

\(1 \le N \le 50\)\(1 \le M \le 1000\)\(1\le W \le 10^5\)\(1 \le Q \le 10^5\)

Solution

考虑把密度的表达式表示出来,设最后答案密度为 \(r\) ,边数量为 \(e\),边权为 \(w[1] , w[2] , w[3] , ... w[e]\),则可列出等式

\(\qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \cfrac{\sum_{i=1}^e w[i]}{e} - r = 0\)

则通分得

\(\qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \cfrac{\sum_{i=1}^e w[i] - e \times r}{e} = 0\)

合并后则得

\(\qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \cfrac{\sum_{i=1}^e (w[i] - r)}{e} = 0\)

\(e\) 也不为 \(0\) 所以

\(\qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \sum_{i=1}^e (w[i] - r) = 0\)

现在可以看出,影响该式值的就是 \((w[i] - r)\)

由于问的是最小值,可以考虑二分其中的 \(r\),根据上式,把图中每条边都减去二分的 \(r\) 值,求出最短路判断上式的值与0的关系,若大于 \(0\) 则说明取小了,找右半区间,否则找左半区间即可。

注意这题的询问次数很多,而点数 \(n\) 与边数 \(m\) 乘积很小,所以要先预处理每一个点对的结果,在询问时 \(O(1)\) 回答询问。

若不预处理最差复杂度 \((80pts)\)\(O(Qnmlogn)\)

预处理最差复杂度 \((100pts)\)\(O({n^{2}}{m^{2}}logn)\)

Code:

// by youyou2007 in 2022.
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 55;
int n, m;
int Q;
struct node{
	int v;
	double w;
};
vector  g[N], g2[N];
queue  que;
double dis[N], ans[N][N];
int b[N];
double total = 0.00000;
int read()
{
    int x = 0, flag = 1;
    char ch = 0;
    while(!isdigit(ch)){ch = getchar(); if(ch == '-') flag = -1;}
    while(isdigit(ch)) {x = (x << 3) + ( x<< 1) + ch - '0'; ch = getchar();}
    return x*flag;
}
int spfa(double mid, int s, int e)//有负数,spfa求最短路
{
	for(int i = 1; i <= n; i++)
	{
		dis[i] = 99999999999.00000;
	}
	memset(b, 0, sizeof b);
	dis[s] = 0.00000;
	b[s] = 1;
	que.push(s);
	while(!que.empty())
	{
		int uu = que.front();
		que.pop();
		b[uu] = 0;
		for(int i = 0; i < g[uu].size(); i++)
		{
			int vv = g[uu][i].v;
			double ww = g[uu][i].w;
			if(dis[vv] > dis[uu] + ww - mid)//这里要 - mid
			{
				dis[vv] = dis[uu] + ww - mid;
				que.push(vv);
				b[vv] = 1;
			}
		}
	}
}
int main()
{
	n = read();
	m = read();
	rep(i, 1, m)
	{
		int x, y;
		double z;
		x = read();
		y = read();
		scanf("%lf", &z);
		g[x].push_back((node){y, z});
		total += z;
	}
	rep(i, 1, n)
	{
		rep(j, 1, n)//预处理
		{
			if(i != j)
			{
				spfa(0, i, j);//先跑一遍,判断是否能到达
				if(dis[j] == 99999999999.00000)
				{
					ans[i][j] = -1.0000;//无法到达把答案记为-1				
					continue;
				}
				double anss = 0.00000, low = 0.00000, mid, high = total * 1.00000;
				while(high - low >= 0.00000)//浮点数二分要注意!
				{
					mid = (low + high) / 2.00000;
					spfa(mid, i, j);
					if(dis[j] > 0)
					{
						low = mid + 0.000001;//这里就不是+1了,尽量精确些!
						anss = mid;
					}
					else
					{
						high = mid - 0.000001;
					}
				}
				ans[i][j] = anss;
			}
		}
	}
	Q = read(); 
	while(Q--)
	{
		int x, y, f = 0;
		x = read();
		y = read();
		if(ans[x][y] == -1) puts("OMG!");
		else printf("%.3lf\n", ans[x][y]);
	}
}