二分+二分图匹配+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 "); 107 } else { 108 printf("%d ", 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 "); 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 ", v / d + 1); 29 return; 30 } 31 } 32 } 33 printf("impossible "); 34 }