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\) ,

否则就会少增广.

完结撒花~

posted @ 2021-08-22 16:26  sshadows  阅读(50)  评论(0编辑  收藏  举报