(好题)POJ3057

二分+二分图匹配+BFS


题意:

墙壁“X”,空区域(都是人)“.”, 门“D”。

人向门移动通过时视为逃脱,门每秒能出去一个人,人可以上下左右移动,墙阻止移动。

求最优移动方案下,最后一个人逃脱的最短时间。如果有人无法安全逃脱(比如被墙围困住),则输出“impossible”。
 
解法1:
以每个门为起点可以通过BFS来算出每个人逃离的最短路。
二分答案,判断所有的人能否在时间T内逃脱。
考虑某一个门,如果能在时间t从该门逃脱的人,应该是距离该门t以内的人,并且其中只有一人能够从该门逃脱。
每个时间和门的二元组,都能确定一个对应的能够从中逃脱的人的集合,而通过计算这个 二元组和人组成的二分图的最大匹配数,我们就能判断所有人是否逃脱。
会T。
  1 const int dx[4] = {0, 1, 0, -1};
  2 const int dy[4] = {1, 0, -1, 0};
  3 
  4 int X, Y;
  5 char field[MAXN][MAXN];
  6 
  7 vector<int> dX, dY;
  8 vector<int> pX, pY;
  9 int dist[MAXN][MAXN][MAXN][MAXN];
 10 
 11 const int MAXV = 10001;
 12 int V;                //顶点数
 13 vector<int> G[MAXV];  //图的邻接表表示
 14 int match[MAXV];      //所匹配的定点
 15 bool used[MAXV];      // DFS中用到的访问标记
 16 
 17 //添加无向边,注意顶点编号从0开始
 18 void add_edge(int u, int v) {
 19     G[u].push_back(v);
 20     G[v].push_back(u);
 21 }
 22 
 23 //通过DFS寻找增广路
 24 bool dfs(int v) {
 25     used[v] = true;
 26     for (int i = 0; i < G[v].size(); i++) {
 27         int u = G[v][i], w = match[u];
 28         if (w < 0 || !used[w] && dfs(w)) {
 29             match[v] = u;
 30             match[u] = v;
 31             return true;
 32         }
 33     }
 34     return false;
 35 }
 36 
 37 //二分图最大匹配,返回最大匹配数
 38 int Bipartite_Matching() {
 39     int res = 0;
 40     memset(match, -1, sizeof(match));
 41     for (int v = 0; v < V; v++) {
 42         if (match[v] < 0) {
 43             memset(used, 0, sizeof(used));
 44             if (dfs(v)) {
 45                 res++;
 46             }
 47         }
 48     }
 49     return res;
 50 }
 51 
 52 // 二分,这一点非常精妙
 53 bool C(int t) {
 54     // 0~d-1        时间1跑掉的人
 55     // d~2d-1       时间2跑掉的人
 56     // (t-1)d~td-1  时间t跑掉的人
 57     // td~td+p-1    人
 58     int d = dX.size(), p = pY.size();
 59     V = t * d + p;
 60     REP(i, 0, V) G[i].clear();
 61     REP(i, 0, d) REP(j, 0, p) {
 62         int o = dist[dX[i]][dY[i]][pX[j]][pY[j]];
 63         if (o >= 0) {
 64             REP(k, o, t + 1)
 65             add_edge((k - 1) * d + i, t * d + j);
 66         }
 67     }
 68     return Bipartite_Matching() == p;
 69 }
 70 
 71 void bfs(int x, int y, int d[MAXN][MAXN]) {
 72     queue<int> qx, qy;
 73     d[x][y] = 0;
 74     qx.push(x);
 75     qy.push(y);
 76     while (!qx.empty()) {
 77         x = qx.front(), qx.pop();
 78         y = qy.front(), qy.pop();
 79         REP(i, 0, 4) {
 80             int nx = x + dx[i];
 81             int ny = y + dy[i];
 82             if (nx >= 0 && nx < X && ny >= 0 && ny < Y &&
 83                 field[nx][ny] == '.' && d[nx][ny] < 0) {
 84                 d[nx][ny] = d[x][y] + 1;
 85                 qx.push(nx);
 86                 qy.push(ny);
 87             }
 88         }
 89     }
 90 }
 91 
 92 void solve() {
 93     int n = X * Y;
 94 
 95     //跑最短路
 96     REP(i, 0, dX.size()) bfs(dX[i], dY[i], dist[dX[i]][dY[i]]);
 97 
 98     //最短路
 99     int lb = -1, ub = n + 1;
100     while (ub - lb > 1) {
101         int mid = (ub + lb) / 2;
102         C(mid) ? ub = mid : lb = mid;
103     }
104 
105     if (ub > n) {
106         printf("impossibe\n");
107     } else {
108         printf("%d\n", ub);
109     }
110 }
111 
112 int main() {
113 #ifndef ONLINE_JUDGE
114     // freopen("input.txt", "r", stdin);
115 #endif  // !ONLINE_JUDGE
116     int T = READ();
117     while (T--) {
118         dX.clear(), dY.clear();
119         pX.clear(), pY.clear();
120         CLR(field), SET(dist);
121         X = READ();
122         Y = READ();
123         REP(i, 0, X) cin >> field[i];
124         REP(i, 0, X) REP(j, 0, Y) {
125             if (field[i][j] == 'D') {
126                 dX.push_back(i);
127                 dY.push_back(j);
128             } else if (field[i][j] == '.') {
129                 pX.push_back(i);
130                 pY.push_back(j);
131             }
132         }
133         solve();
134     }
135     return 0;
136 }

解法2:

在上述解法下,重复的操作是每次二分都会计算一遍匹配,所以T,那么当T增加时,根据增广路的性质,我们只需要不断增加T,每次加1,就可以找出答案。

这个其实现在也不怎么看懂。

下面是不同的地方,一样的没放上来

 1 void solve() {
 2     int n = X * Y;
 3 
 4     //跑最短路
 5     REP(i, 0, dX.size()) bfs(dX[i], dY[i], dist[dX[i]][dY[i]]);
 6 
 7     // 加边
 8     int d = dX.size(), p = pX.size();
 9     V = X * Y * d + p;
10     for (int v = 0; v < V; ++v) G[v].clear();
11     REP(i, 0, d) REP(j, 0, p) {
12         int o = dist[dX[i]][dY[i]][pX[j]][pY[j]];
13         if (o >= 0) REP(k, o, n + 1) add_edge((k - 1) * d + i, n * d + j);
14     }
15 
16     // 匹配
17     if (p == 0) {
18         printf("0\n");
19         return;
20     }
21     int num = 0;  // 匹配数
22     memset(match, -1, sizeof(match));
23     for (int v = 0; v < n * d; v++) {
24         // n*d是节点总数,每个门一个,把所有情况都跑一遍
25         memset(used, 0, sizeof(used));
26         if (dfs(v)) {
27             if (++num == p) {
28                 printf("%d\n", v / d + 1);
29                 return;
30             }
31         }
32     }
33     printf("impossible\n");
34 }

猜你喜欢

转载自www.cnblogs.com/romaLzhih/p/12321063.html