【Nowcoder - 5666 H Minimum-cost Flow】2020牛客暑期多校训练营(第一场)【最小费用流变形】

题意

给定一个 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) 2n50,1m100,1ci105,1q105,0uivi109,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+ans30.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;
}

你可能感兴趣的:(网络流,牛客多校训练营第一场,最小费用流变形)