ZOJ-1654 Place the Robots 拆行拆列构图+二分匹配 Or 最大独立点集+TLE
题意:给定一个方格,格子中的每点由空地,草地和墙组成,每个空地可以放置一个机器人,每个机器人能够向四个方向扫射激光,所以要给定一种方案能够在棋盘上放置足够多的机器人。激光可以穿过草地但是不能够穿过墙。
解法:这题其实就是经典的二分匹配构图题,做法是将行和列进行拆分,也即如果某一行中有一堵墙,那么将墙前面和后面视为不同的一行,对列进行同样的处理,那么每个空地就需要获得一个新的行和列属性,通过遍历整个图来给每一块空地分配一个行和列号。分配要尽可能的紧凑。对于任何一块空地,要占用一个行和一个列(意味着该行和该列内不能够再容下其他空地)对于每一块空地,将其所对应的行号和列号分为图两个部分,构成二分图。二分图中边的含义就是某一行和某一列匹配,也即一组边对应一组坐标,对应一个能够放置棋子的空地,最大匹配即为在每一行和每一列最多匹配一次的情况下最多放置多少个棋子。
采用以下方式进行编号:
行和列编号数组不小心使用char数组,段错误到崩溃......
代码如下:
View Code
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> using namespace std; const int MaxN = 60; int N, M, rnum, cnum; char mp[MaxN][MaxN]; int rn[MaxN][MaxN], cn[MaxN][MaxN]; // rn用来记录某一个点在拆行后的行号,同理cn用来记录某一个点在拆列后的列号 void getr() { // 该函数能够为每一个空地分配拆行后的行号 memset(rn, 0xff, sizeof (rn)); rnum = 1; for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { if (mp[i][j] == 'o') { rn[i][j] = rnum; while (++j < M && mp[i][j] != '#') { if (mp[i][j] == 'o') rn[i][j] = rnum; } // 处理位于同一行的并以空地开头的行视为一行,其中为某些草地具有连通性 --j, ++rnum; } } } } void getc() { // 该函数能够为一块空地分配一个拆列后的列号 memset(cn, 0xff, sizeof (cn)); cnum = 1; for (int j = 0; j < M; ++j) { for (int i = 0; i < N; ++i) { if (mp[i][j] == 'o') { cn[i][j] = cnum; while (++i < N && mp[i][j] != '#') { if (mp[i][j] == 'o') cn[i][j] = cnum; } --i, ++cnum; } } } } char G[MaxN*MaxN][MaxN*MaxN], vis[MaxN*MaxN]; int match[MaxN*MaxN]; void build() { getr(); getc(); memset(G, 0, sizeof (G)); for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { if (mp[i][j] == 'o') { G[rn[i][j]][cn[i][j]] = 1; // 一块空地占用一行和一列 } } } } bool path(int u) { for (int i = 1; i < cnum; ++i) { if (!G[u][i] || vis[i]) continue; vis[i] = 1; if (!match[i] || path(match[i])) { match[i] = u; return true; } } return false; } int query() { int ret = 0; memset(match, 0, sizeof (match)); for (int i = 1; i < rnum; ++i) { memset(vis, 0, sizeof (vis)); if (path(i)) { ++ret; } } return ret; } /* 5 5 o***# *###* oo#oo ***#o #o**o 4 */ int main() { int T, ca = 0; scanf("%d", &T); while (T--) { scanf("%d %d", &N, &M); for (int i = 0; i < N; ++i) { scanf("%s", mp[i]); } printf("Case :%d\n%d\n", ++ca, (build(), query())); } return 0; }
贴个最大团TLE代码:
View Code
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> using namespace std; int N, M, nd[2505], idx, G[2505][2505]; char mp[55][55]; bool check(int a, int b) { int x1 = nd[a]/N, y1 = nd[a]%N; int x2 = nd[b]/N, y2 = nd[b]%N; if (x1 != x2 && y1 != y2) return true; if (x1 == x2) { // 如果是在同一行 for (int i = y1+1; i < y2; ++i) { if (mp[x1][i] == '#') { return true; } } } else if (y1 == y2) { for (int i = x1+1; i < x2; ++i) { if (mp[i][y1] == '#') { return true; } } } return false; } void build() { memset(G, 0, sizeof (G)); for (int i = 0; i < idx; ++i) { for (int j = i+1; j < idx; ++j) { G[i][j] = G[j][i] = check(i, j); } } } int ret, cnt[2505], st[2505]; void dfs(int x, int num) { for (int i = x+1; i < idx; ++i) { if (!G[x][i]) continue; if (cnt[i] + num <= ret) return; int flag = true; for (int j = 0; j < num; ++j) { if (!G[i][st[j]]) { flag = false; break; } } if (flag) { st[num] = i; dfs(i, num+1); } } if (num > ret) { ret = num; } } int query() { ret = 0; for (int i = idx-1; i >= 0; --i) { st[0] = i; dfs(i, 1); cnt[i] = ret; } return ret; } int main() { int T, ca = 0; scanf("%d", &T); while (T--) { scanf("%d %d", &N, &M); idx = 0; for (int i = 0; i < N; ++i) { scanf("%s", mp[i]); for (int j = 0; j < M; ++j) { if (mp[i][j] == 'o') { nd[idx++] = i*N+j; } } } build(); printf("Case :%d\n%d\n", ++ca, query()); } return 0; }