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 <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#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 <node> g[N], g2[N];
queue <int> 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]);
}
return 0;
}