题目链接:Finding Seats Again
题目大意:
给定一个由数字和 . . .组成的网格(数字一定是一个个位数),数字代表周围某个长方形子网格中需要全部填入相同字母,你的任务是输出任意一组解。
题解:
首先容易想到的是依次考虑每个数字,然后对这个数字进行因式分解,这样可以得到矩形的长和宽,然后在考虑这个矩形的位置,大致能得到这样一份代码:
遗憾的是上面的代码时间复杂度太高没能通过测试。上述代码超时的关键在于第三个循环太过于低效,它并不能充分的利用之前的信息。例如上一轮发现有些地方填写了字母,而在下一轮考虑矩阵的位置,可能上一轮填写字母的位置没有移出去,这应该直接判断不能填写,而上述的代码还需要进行循环进行判断。
一种高效的方式是直接枚举举行而不是在固定的先限制了矩形的大小,枚举出来矩形能够进行下一次搜索的条件是:矩形中只有一个数字,并且矩形的面积等于这个数字。这样为什么能变得高效呢?一是当我们发现矩形里面存在两个数字的时候,下一次的枚举条件可以进行收缩:即将下一次列的最大值压缩到当前结束时的值,这是因为如果下一次枚举矩形超过了这个位置很明显也会存在两个数字。二是当我们发现矩形中有一个位置被填入了字母,那么下一次矩形的列的最大值依然应该是当前结束时的值,道理与上述相同。三是如果只有一个数字但是数字的值小于了当前矩阵的面积,我们依然可以让列的限制变为当前列的枚举值。
上述的方法能够大大缩减枚举状态的多少,但是具体的复杂度是多少我也不会算。
代码:
#include <bits/stdc++.h>
const int MAXN = 20;
using namespace std;
int n, m;
char table[MAXN][MAXN];
char ans[MAXN][MAXN];
void fill(int a, int b, int x, int y, char ch)
{
for (int dx = 0; dx < a; dx++) {
for (int dy = 0; dy < b; dy++) {
ans[x + dx][y + dy] = ch;
}
}
}
bool dfs(int id, char ch)
{
while (ans[id / n][id % n] != 0) {
id++; }
if (id == n * n) {
return true; }
int x = id / n;
int y = id % n;
int maxColumn = n;
for (int a = 1; a + x - 1 < n; a++) {
for (int b = 1; b + y - 1 < maxColumn; b++) {
if (ans[a + x - 1][b + y - 1] != 0) {
maxColumn = b + y - 1; // 当前这一列不行的话,由于a是递增的所以下一次这一列之后没有必要枚举
break;
}
int digitCnt = 0, digit = 0;
for (int dx = 0; dx < a; dx++) {
for (int dy = 0; dy < b; dy++) {
if (isdigit(table[x + dx][y + dy])) {
digitCnt++;
digit = table[x + dx][y + dy] - '0';
if (digitCnt >= 2) {
break; }
}
}
if (digitCnt >= 2) {
break; }
}
if (digitCnt <= 0 || (digitCnt == 1 && a * b < digit)) {
continue; }
if (digitCnt >= 2 || (digitCnt == 1 && a * b > digit)) {
// 与上面同样的道理,之后的这一列也不会成立
maxColumn = b + y - 1;
break;
}
fill(a, b, x, y, ch);
if (dfs(id, ch + 1)) {
return true; }
fill(a, b, x, y, 0);
}
}
return false;
}
int main()
{
while (cin >> n >> m && n != 0 && m != 0) {
memset(ans, 0, sizeof(ans));
for (int i = 0; i < n; i++) {
cin >> table[i]; }
dfs(0, 'A');
for (int i = 0; i < n; i++) {
cout << ans[i] << endl; }
}
return 0;
}