POJ 3051 bfs 二部图最大匹配

题目链接戳这里
题意:有一个X*Y的区域,每块可能是墙壁‘X'或者是空的'.',或者门'D',每个空位有1个人,上下左右4个方向移动要1s,每个门1秒钟只能通过一个人,问所有人最快出去要多久?有人出不去就输出impossible。

方法:
第一种思路:利用二分法判断T时刻所有人能否出去,从而找最短时间。那么,如何判断T时刻所有人是否都能出去?考虑某一个人,他有许多选择:某一个t1时刻能出往d1号门,t2时刻能出往d2号门...(tmax <= T)即我们可以根据人和 (时间,门)的连边形成一个二部图,那么最大匹配即是在T时刻能出去的人数了。
于是可以写出下面这样一大串代码:

// #include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;

#define pii pair<int,int>
#define ll long long
#define mst(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(ll i=(a);i<(b);++i)
#define rrep(i,a,b) for(ll i=(b-1);i>=a;--i)
#define fi first
#define se second
#define sz size()
#define pb push_back
const double eps = 1e-8, PI = acos(-1.0f);
const int inf = 0x3f3f3f3f, maxN = 1e5 + 5;
const int maxX = 15, maxY = 15;
int N, M, T;

const int dx[] = {0, 0, -1, 1};
const int dy[] = {1, -1, 0, 0};

// Input
int X, Y;
char field[maxX][maxY + 1];

vector<pii> door;     // 门的坐标
vector<pii> peop;     // 人的坐标
int dist[maxX][maxY][maxX][maxY];

vector<int> G[maxN];
int match[maxN];
bool used[maxN];
void add_edge(int x, int y) {
    G[x].pb(y);
    G[y].pb(x);
}

int dfs(int v) {
    used[v] = 1;
    rep(i, 0, G[v].sz) {
        int u = G[v][i], w = match[u];
        if (w < 0 || (!used[w] && dfs(w))) {
            match[v] = u;
            match[u] = v;
            return 1;
        }
    }
    return 0;
}

int bipratite_matching() {
    int ret = 0;
    mst(match, -1);
    rep(i, 0, N) {
        if (match[i] < 0) {
            mst(used, 0);
            if (dfs(i))
                ++ret;
        }
    }
    return ret;
}

// 判断所有人是否能够在时间t以内安全逃离
bool ok(int t) {
    int d = door.sz, p = peop.sz;
    // 0~d-1    时间1对应的门
    // d~2d-1   时间2对应的门
    // (t-1)d~td-1  时间t对应的门
    // td~td+p-1    人
    
    N = t * d + p;

    rep(v, 0, N) G[v].clear();
    rep(i, 0, d) {
        rep(j, 0, p) {
            int dis = dist[door[i].fi][door[i].se][peop[j].fi][peop[j].se];
            if (dis >= 0) {
                rep(k, dis, t + 1) {
                    add_edge((k - 1) * d + i, t * d + j);
                }
            }
        }
    }
    int ans = bipratite_matching();
    return ans == p;
}

void bfs(int x, int y, int d[maxX][maxY]) {
    queue<pii> Q;
    d[x][y] = 0;
    Q.push(pii(x, y));

    while (Q.sz) {
        pii cur = Q.front(); Q.pop();
        rep(k, 0, 4) {
            int nx = cur.fi + dx[k], ny = cur.se + dy[k];
            if (0 <= nx && nx < X && 0 <= ny && ny < Y
                    && field[nx][ny] == '.' && d[nx][ny] < 0) {
                d[nx][ny] = d[cur.fi][cur.se] + 1;
                Q.push(pii(nx, ny));
            }
        }
    }
}

void solve() {
    int n = X * Y;
    door.clear(); peop.clear();
    mst(dist, -1);

    // 计算到各个门的最近距离 
    rep(x, 0, X) {
        rep(y, 0, Y) {
            if (field[x][y] == 'D') {
                door.pb(pii(x, y));
                bfs(x, y, dist[x][y]);
            } else if (field[x][y] == '.') {
                peop.pb(pii(x, y));
            }
        }
    }

    // 二分法
    int le = -1, ri = n + 1;
    while (ri - le > 1) {
        int mid = (ri + le) / 2;
        if (ok(mid)) ri = mid;
        else le = mid;
    }
    if (ri == n + 1)
        puts("impossible");
    else {
        printf("%d\n", ri);
    }
}

int main() {
    // freopen("data.in", "r", stdin);
    scanf("%d", &T);

    while (T--) {
        scanf("%d %d\n", &X, &Y);
        rep(i, 0, X)
            scanf("%s", field[i]);
        solve();
    }
    return 0;
}

但是会超时。因为每一次二分,都得重新根据判断的时间上限T来构图。其实!要啥二分啊,还要啥二分?直接拿所有的时间和门组成的元组和人都进行一个连边,形成一个二部图,然后直接进行一个最大匹配。求最大匹配过程中,若是匹配数够了人数,直接输出当前时间即可。

这里解释两个东西:

  1. 时间和门组成的元素=n*d,而n=X*Y。为何?其实这里相当于认为最大时间是X*Y,实际比这小一些,因为如果可以成功都出去,最大时间应该是“所有人”从一个门出去的时间,这里取大了是无妨的。

  2. 如果匹配数够了p人,那么为啥输出v/d+1?因为我们开始匹配的一侧是(时间,门号)元组,它的编号是“当前时间 * 总门数 + 门号",求时间那在代码里就是v/d+1了。代码如下,最后,别忘记每个case的vector等要初始化好。

// #include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;

#define pii pair<int,int>
#define ll long long
#define mst(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(ll i=(a);i<(b);++i)
#define rrep(i,a,b) for(ll i=(b-1);i>=a;--i)
#define fi first
#define se second
#define sz size()
#define lb lower_bound
#define ub upper_bound
#define pb push_back
const double eps = 1e-8, PI = acos(-1.0f);
const int inf = 0x3f3f3f3f, maxN = 1e5 + 5;
const int maxX = 15, maxY = 15;
int N, M, T;

const int dx[] = {0, 0, -1, 1};
const int dy[] = {1, -1, 0, 0};

// Input
int X, Y;
char field[maxX][maxY + 1];

vector<pii> door;     // 门的坐标
vector<pii> peop;     // 人的坐标
int dist[maxX][maxY][maxX][maxY];

vector<int> G[maxN];
int match[maxN];
bool used[maxN];
void add_edge(int x, int y) {
    G[x].pb(y);
    G[y].pb(x);
}

int dfs(int v) {
    used[v] = 1;
    rep(i, 0, G[v].sz) {
        int u = G[v][i], w = match[u];
        if (w < 0 || (!used[w] && dfs(w))) {
            match[v] = u;
            match[u] = v;
            return 1;
        }
    }
    return 0;
}

void bfs(int x, int y, int d[maxX][maxY]) {
    queue<pii> Q;
    d[x][y] = 0;
    Q.push(pii(x, y));

    while (Q.sz) {
        pii cur = Q.front(); Q.pop();
        rep(k, 0, 4) {
            int nx = cur.fi + dx[k], ny = cur.se + dy[k];
            if (0 <= nx && nx < X && 0 <= ny && ny < Y
                    && field[nx][ny] == '.' && d[nx][ny] < 0) {
                d[nx][ny] = d[cur.fi][cur.se] + 1;
                Q.push(pii(nx, ny));
            }
        }
    }
}

void solve() {
    int n = X * Y;
    door.clear(); peop.clear();
    mst(dist, -1);

    // 计算到各个门的最近距离 
    rep(x, 0, X) {
        rep(y, 0, Y) {
            if (field[x][y] == 'D') {
                door.pb(pii(x, y));
                bfs(x, y, dist[x][y]);
            } else if (field[x][y] == '.') {
                peop.pb(pii(x, y));
            }
        }
    }

    // 建立图
    int d = door.sz, p = peop.sz;
    N = X * Y * d + p;
    rep(v, 0, N) G[v].clear();

    rep(i, 0, d) {
        rep(j, 0, p) {
            int dis = dist[door[i].fi][door[i].se][peop[j].fi][peop[j].se];
            if (dis >= 0) {
                rep(k, dis, n + 1)
                    add_edge((k - 1) * d + i, n * d + j);
            }
        }
    }

    if (p == 0) {
        puts("0");
        return;
    }
    int num = 0;
    mst(match, -1);
    rep(v, 0, n * d) {
        mst(used, 0);
        if (dfs(v)) {
            if (++num == p) {
                printf("%lld\n", v / d + 1);
                return;
            }
        }
    }
    puts("impossible");
}

int main() {
    // freopen("data.in", "r", stdin);
    scanf("%d", &T);

    while (T--) {
        scanf("%d %d\n", &X, &Y);
        rep(i, 0, X)
            scanf("%s", field[i]);
        solve();
    }
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • 1. 关于诊断X线机准直器的作用,错误的是()。 (6.0 分) A. 显示照射野 B. 显示中心线 C. 屏蔽多...
    我们村我最帅阅读 10,350评论 0 5
  • “原来你在这里” “我在这里”。这是一个只有两个人的简单的故事,他是她的摆渡人,但是她却摆渡了他的人生。故事...
    雨霁彩彻阅读 557评论 0 1
  • 我把头抵在玻璃上,身体靠在窗边,目不转睛的看着对面的公寓。 起初那栋三十层的高楼只亮起了一盏灯,像是一只突然从长夜...
    蒙之吃吃阅读 244评论 0 0
  • 工作是最好的修行。 在没有暴富之前,为了吃饭,很多人“不得不”工作,每天的工作都是为了盼望假期和突然的奖金。 对于...
    河堤守望者阅读 6,010评论 0 1
  • 清晨急着去老家看有病的娘,我只好和去学校的老公一起出来,太早店里寄存的东西拿不出来,我在一所油饼店旁停下等。一...
    闲云好运阅读 273评论 0 0