给定一个 n n n 个点, m m m 条边的有向图,每条边的费用为 c i c_i ci。一共 q q q 次查询,每次查询给定 u i , v i u_i, v_i ui,vi,表示图中每条边的容量为 u i v i \displaystyle\frac{u_i}{v_i} viui,询问从 1 1 1 号点到 n n n 号点,流量为 1 1 1 时的最小花费。
( 2 ≤ n ≤ 50 , 1 ≤ m ≤ 100 , 1 ≤ c i ≤ 1 0 5 , 1 ≤ q ≤ 1 0 5 , 0 ≤ u i ≤ v i ≤ 1 0 9 , v i > 0 ) (2\leq n\leq 50,1\leq m\leq 100,1\leq c_i\leq 10^5,1\leq q\leq 10^5,0\leq u_i\leq v_i\leq 10^9,v_i>0) (2≤n≤50,1≤m≤100,1≤ci≤105,1≤q≤105,0≤ui≤vi≤109,vi>0)
题目链接: l i n k link link
看完题后,最直接的想法就是将容量和流量都乘一个 v i v_i vi,然后暴力跑最小费用流。这样的话需要跑 q q q 次,显然是超时的。
比较明显可以看出,如果不想超时,那么本题最多跑一次费用流。所以问题就在于如何通过只跑一次费用流就求出所有询问的答案呢,并且如果只跑一次费用流,那么每条边的容量应该是多少呢?
在这种思考角度下,我们大概可以猜到只跑一次费用流,每条边的容量应该设为 1 1 1,于是开始思考如何进行转换。
这里引用一下题解的转换方法,令 c o s t ( c , f ) cost(c,f) cost(c,f) 表示图中每条边的容量为 c c c,总流量为 f f f 的最小花费,则 c o s t ( c , f ) = c o s t ( 1 , f / c ) ∗ c cost(c,f)=cost(1,f/c)*c cost(c,f)=cost(1,f/c)∗c,又因为本题 f = 1 f=1 f=1,因此问题转化为 c o s t ( 1 , 1 / c ) ∗ c cost(1,1/c)*c cost(1,1/c)∗c。
即对于 u i , v i u_i,v_i ui,vi,问题转化为求 c o s t ( 1 , v i u i ) ∗ u i v i cost(1,\displaystyle\frac{v_i}{u_i})*\displaystyle\frac{u_i}{v_i} cost(1,uivi)∗viui。
思考一下转化后的这个模型,所有边的容量均为 1 1 1,那么不难发现,最小费用流每次增广的容量也一定为 1 1 1,增广 v i u i \displaystyle\frac{v_i}{u_i} uivi 次后,即达到最小值。因此我们需要跑一次最小费用流,记录每一次增广的最小花费。例如 v i = 5 , u i = 2 v_i=5,u_i=2 vi=5,ui=2 的话,答案则为 ( a n s 1 + a n s 2 + a n s 3 ∗ 0.5 ) ∗ 2 5 (ans_1+ans_2+ans_3*0.5)*\displaystyle\frac{2}{5} (ans1+ans2+ans3∗0.5)∗52,其中 a n s i ans_i ansi 表示第 i i i 次增广的最小花费。
这里需要注意,由于一共只有 100 100 100 条边,因此最多只能增广 100 100 100 次,令最大增广次数为 c n t cnt cnt,则如果 v i u i > c n t \displaystyle\frac{v_i}{u_i}>cnt uivi>cnt,则输出 N a N NaN NaN。
本题其实并不是很难,比赛时没做出来的确不太应该。仔细反思一下,可能还是对问题的研究与分析能力不够强,不会对于一个问题进行细致的抽丝剥茧,然后再一一分析攻破,在之后的训练中需要着重关注这一项能力。
#include
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
const int N = 105, M = 1e3+10;
const db EPS = 1e-9;
using namespace std;
void dbg() {
cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {
cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}
struct Edge {
int to,next,cap,cost;
}e[M];
int head[N], tot, s, t;
int dis[N], incf[N], pre[N], vis[N];
int n, m, maxflow, cnt;
ll ans[N];
void init() {
tot = 1, cnt = 0;
memset(head, 0, sizeof head);
memset(ans, 0, sizeof ans);
}
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a%b);
}
void add(int x, int y, int z, int c) {
e[++tot].to = y, e[tot].next = head[x], head[x] = tot, e[tot].cap = z, e[tot].cost = c;
e[++tot].to = x, e[tot].next = head[y], head[y] = tot, e[tot].cap = 0, e[tot].cost = -c;
}
bool spfa() {
queue<int> q;
fill(dis,dis+N,5e8);
memset(vis,0,sizeof vis);
q.push(s); dis[s] = 0; vis[s] = 1;
incf[s] = 1<<30;
while(q.size()) {
int x = q.front(); vis[x] = 0; q.pop();
for(int i = head[x]; i; i = e[i].next) {
if(!e[i].cap) continue;
int y = e[i].to;
if(dis[y] > dis[x]+e[i].cost) {
dis[y] = dis[x]+e[i].cost;
incf[y] = min(incf[x], e[i].cap);
pre[y] = i;
if(!vis[y]) vis[y] = 1, q.push(y);
}
}
}
if(dis[t] == 5e8) return false;
return true;
}
void update() {
int x = t;
while(x != s) {
int i = pre[x];
e[i].cap -= incf[t];
e[i^1].cap += incf[t];
x = e[i^1].to;
}
maxflow += incf[t];
ans[++cnt] = (ll)dis[t]*incf[t];
}
void solve() {
maxflow = 0; s = 1, t = n;
while(spfa()) update();
rep(i,1,cnt) ans[i] += ans[i-1];
}
int main()
{
while(~scanf("%d%d",&n,&m)) {
init();
rep(i,1,m) {
int a,b,c; scanf("%d%d%d",&a,&b,&c);
add(a,b,1,c);
}
solve();
int q; scanf("%d",&q);
rep(i,1,q) {
ll u,v; scanf("%lld%lld",&u,&v);
if(v > u*cnt) {
printf("NaN\n");
}
else {
ll h = v/u, r1 = ans[h]*u, r2 = v;
if(u*h < v) {
ll p = v-u*h;
ll tp = ans[h+1]-ans[h];
r1 += p*tp;
}
ll g = gcd(r1, r2);
printf("%lld/%lld\n", r1/g, r2/g);
}
}
}
return 0;
}