UVA11248 Frequency Hopping

最近又在水网络流qwq

但是以后不卷了

老巴实的做些我能做的水题去了qwq

这道题不难想,

但是不太好实现(其实也不难实现,

首先,

我们直接跑一遍最大流,

如果 \(maxflow \geq c\) 的话,

我们就直接输出 \(possible\) 就是了.

很显然,

我们一定存在一个流与 \(c\) 相等,

因为我们的流量是可以取到 \(0 \sim maxflow\) 的所有值的.

比较麻烦的是下面,

在我们的 \(maxflow < s\) 的时候,

修改哪些边.

其实也不难,

我们想一下,

限制我们最大流的是哪些边,

或者说,

哪些边已经跑满了(流量 == 容量),

很显然,

就是最小割里的边,

我们只需要把最小割里的边拿出来,

按照题目中给的要求排序,

再分别修改它们的容量为 \(c\) ,

修改之后,

跑最大流,

注意一下,

我们不必每次都重新跑最大流,

我们只需要在我们跑完最大流后的图上跑就可以了,

因为原先存在的流量肯定都还存在,

我们只需要在当前的 \(maxflow\) 的基础上进行增广就可以了.

(所以记得把当前所有边的流量存一份)

这边建议使用 \(dinic\) 算法,

因为它不需要任何修改,

也不需要其他预处理,

像 \(ISAP\) 就需要重新预处理,

于是就有了如下代码.

\(code:\)

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 4e4 + 5, inf = 1 << 30;
int now[maxn], d[maxn], s = 1, t, n;
int tot, from[maxn], to[maxn], nxt[maxn], cap[maxn], head[maxn], faq[maxn];//faq用来存储跑完最大流后的所有边的流量,方便以后使用
ll maxflow;
queue <int> q;
struct node{//用来储存最小割中的边
	int from, to, id;
	bool operator < (const node& x) const {//根据题目中的要求重载运算符
		if (from == x.from) return to < x.to;
		return from < x.from;
	}
} edge[maxn];
int read(){
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9'){
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9'){
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return x * f;
}
void add(int u, int v, int w){
	from[++tot] = u, to[tot] = v, cap[tot] = w, nxt[tot] = head[u], head[u] = tot;
}
bool BFS(){
	memset(d, 0, sizeof(int) * (n + 1));
	while (q.size()) q.pop();
	q.push(s); d[s] = 1; now[s] = head[s];
	while (q.size()){
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = nxt[i]){
			int y = to[i];
			if (!d[y] && cap[i]){
				q.push(y);
				d[y] = d[x] + 1;
				now[y] = head[y];
				if (y == t) return true;
			}
		}
	}
	return false;
}
int dinic(int x, int flow){
	if (x == t || flow == 0) return flow;
	int rest = flow, k, i;
	for (i = now[x]; i && rest; i = nxt[i]){
		int y = to[i];
		if (d[y] == d[x] + 1 && cap[i]){
			k = dinic(y, min(rest, cap[i]));
			if (!k) d[y] = 0;
			cap[i] -= k;
			cap[i ^ 1] += k;
			rest -= k;
		}
	}
	now[x] = i;
	return flow - rest;
}
int judge(){//qwq为修改当前这条弧的容量后增加的流量
	ll flow = 0, qwq = 0;
	while (BFS())//这就是为什么我建议用dinic了,这里直接用就可以,完全没必要改任何一点,而ISAP需要重新初始化
		while (flow = dinic(s, inf)) qwq += flow;
	return qwq;//返回修改后增加的流量
}
int main(){
	n = read(); int e = read(), c = read(), qwq = 1;
	while (n){
		printf("Case %d: ", qwq++);
		tot = 1, maxflow = 0, t = n;
		for (int i = 1; i <= n; i++) head[i] = d[i] = 0;
		for (int i = 1; i <= e; i++){
			int u = read(), v = read(), w = read();
			add(u, v, w); add(v, u, 0);
		}
		ll flow = 0;
		while (BFS())//正常的dinic
			while (flow = dinic(s, inf)) maxflow += flow;
		c -= maxflow;//将c与maxflow作差,这样可以方便之后的操作(我认为)
		if (c <= 0) printf("possible");//maxflow>=c,输出possible
		else {//判断是否存在合法的弧,这里合法的弧指的是这条弧修改后能使得maxflow>=c
			int num = 0;
			for (int i = 2; i <= tot; i++) faq[i] = cap[i];//保存当前图上的所有边的流量
			for (int i = 2; i <= tot; i += 2){//寻找最小割中的边,这里我们只需要找连的边,不需要找我们建的反向边
				if (cap[i] == 0){//根据最大流最小割定理我们可知,最小割中的边的流量都与容量相等
					edge[++num].from = from[i];
					edge[num].to = to[i];
					edge[num].id = i;
				}
			}
			sort(edge + 1, edge + num + 1);//排序,因为我们需要按照题目中要求的顺序输出.
			bool ok = 1;//用来判断是否已经找到合法的弧
			for (int i = 1; i <= num; i++){
				cap[edge[i].id] = c;
				if (judge() >= c){//当前的弧合法
					if (ok){
						ok = 0;
						printf("possible option:(%d,%d)", edge[i].from, edge[i].to);
					}
					else printf(",(%d,%d)", edge[i].from, edge[i].to);
				}
				for (int j = 2; j <= tot; j++) cap[j] = faq[j];
			}
			if (ok) printf("not possible");//没有找到合法的弧
		}
		printf("\n");
		n = read(), e = read(), c = read();
	}
	return 0;
}

其实最开始的时候我犯了一个错误,

就是只找到一条增广路的时候就返回流量,

实际上我们应该跑完这遍 \(DFS\) ,

否则就会少增广.

完结撒花~

上一篇:最大流最小割


下一篇:算法每周一题(一)——单源最短路