CF327D 城市建设
1 CF327D 城市建设
2 题目描述
时间限制 \(2s\) | 空间限制 \(256M\)
\(Iahub\) 开始玩一个叫做“城市建设”的电脑游戏。游戏的目标是在一个 \(n\) 行 \(m\) 列总共 \(n\times m\) 个单元的长方形的格子里修建自己的城市。其中有一些格子中间有孔,不能用来修建任何东西。其他的格子都是空的。在一个格子里 \(Iahub\) 可以修建下列两种类型之一:
- 蓝颜色的塔,每个塔的人口数量限制在 \(100\) 以内;
- 红颜色的塔,每个人口数量限制在 \(200\) 以内。红颜色的塔要求旁边至少有一面和蓝颜色的塔相接。
\(Iahub\) 也可以摧毁任何单元内的建筑,而且摧毁次数也不限。摧毁掉一个建筑后那个建筑所在的单元格变空,其他的单元格不受影响。\(Iahub\) 可以劝说别人去他的城市,所以他需要他的城市能容纳尽可能多的人口。他要找出一个修建的顺序使得总的能容纳的人口数越多越好。请找出最佳修建方法。
数据范围:\(1 ≤ n, m ≤ 500\)
3 题解
很显然,对于每一个有 \(n\) 个节点的联通块,我们最多能建造 \(n-1\) 个红颜色的建筑。那么我们考虑如何建造 \(n-1\) 个红颜色的建筑。容易发现,我们最优的构造方案是先将所有可建造区域全部建造上蓝颜色建筑,然后对于每一个联通块,从边缘开始推倒建筑,建红颜色建筑。如何保证一定存在这样的构造方案且正确地输出呢?我们可以找到一个点,将这个点设为那个唯一的不建红颜色建筑的点(根节点)。容易发现,我们可以用 \(dfs\) 遍历所有联通块内的节点。此时,我们递归中找到的叶子结点变成红颜色建筑后,我们可以顺着遍历到它的那一条边找到它的父节点,进而确定所有除了根节点之外的节点都可以变成红颜色建筑,至于建造次序,我们可以按照上述证明中的方案建造。
此时的总操作次数最多是 \((n\times m-1) \times 3 + 1\)(整个图是一个联通图)。由于 \(n, m \le 100\),所以总操作次数最多在 \(3\times 10^4\) 左右,完全符合题目中的 \(10^6\)。
4 代码(空格警告):
#include <iostream>
#include <queue>
using namespace std;
const int N = 505;
queue< pair<char, pair<int, int> > > q;
int n, m;
string s;
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
char Map[N][N];
bool f[N][N];
bool check(int x, int y)
{
if (x < 1 || y < 1 || x > n || y > m) return 0;
return 1;
}
void dfs1(int x, int y, int flag)
{
if (!check(x, y)) return ;
if (f[x][y]) return ;
f[x][y] = 1;
q.push(make_pair('B', make_pair(x, y)));
for (int i = 0; i < 4; i++)
{
int dx = x + dir[i][0];
int dy = y + dir[i][1];
dfs1(dx, dy, 0);
}
if (!flag)
{
q.push(make_pair('D', make_pair(x, y)));
q.push(make_pair('R', make_pair(x, y)));
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> s;
for (int j = 1; j <= m; j++)
{
Map[i][j] = s[j-1];
if (Map[i][j] == '#') f[i][j] = 1;
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (f[i][j]) continue;
dfs1(i, j, 1);
}
}
cout << q.size() << '\n';
while (q.size())
{
cout << q.front().first << " " << q.front().second.first << " " << q.front().second.second << '\n';
q.pop();
}
return 0;
}