题目链接:Lattice Animals
题目描述:
给定一个 w ∗ h w*h w∗h的网格,以及一个整数 n n n你需要输出能够放在 w ∗ h w*h w∗h的网格中大小为 n n n的联通块的个数。上下翻转,左右翻转,平移后相同的联通块看做一种。
上图的上面是 2 ∗ 4 2*4 2∗4的网格中能够放入大小为 5 5 5的各种联通块,下面是 3 ∗ 3 3*3 3∗3的网格中能够放入的大小为 8 8 8的连通块的个数。
题解:
本题进行暴力枚举即可。本题每次可以选择已有的联通块中的任意一个位置进行上下左右的扩充,当连通块的个数达到 n n n的时候,我们只需要判断当前的联通块是否重复出现过,如果没有重复出现过,那么我们更新答案即可。
如何判断一个联通块是否已经出现过了呢?我们对于搜索到的联通块,首先将其平移到左上角,然后依次对他进行旋转操作,翻转操作判断这个过程中的联通块是否出现过。
如何实现平移操作?平移到左上角,实际上就是横纵坐标减去各自的最小值;
如何实现旋转操作?我们只需要实现顺时针或者逆时针旋转中的一种,我们以顺时针旋转为例,首先我们需要确定的是沿着哪一个方块进行旋转,在这里,我们可以沿着 ( 0 , 0 ) (0, 0) (0,0)进行旋转,不难发现,这样旋转后的横坐标变为原来的纵坐标,旋转后的纵坐标变为原来横坐标的相反数,需要注意的是,我们旋转后还需要进行一次平移操作,将旋转后的图形移动到左上角。
如何实现翻转操作?我们可以以最左边的一列方块为轴进行水平翻转,这样翻转后的横坐标不变,纵坐标变为相反数。我们实际上不需要实现竖直翻转,通过翻转后进行旋转也可以得到出竖直翻转后的图形。
不过这样写会超时,因为 n + 1 n+1 n+1块是由 n n n拓展而来,但是对于新给出的 n n n我们依然会从最开始进行放置,这样就花费了大量的时间进行重复的工作,所以我们需要把出现过的情况给记录下来,供每一组的输入使用,这也就是记忆化搜索的思想,在本题中,可以打表,直接使用一个数组将所有可能的答案保存下来,这样最后的程序只需要 O ( 1 ) O(1) O(1)的复杂的,这里只给出如何计算出各种情况下答案的代码而没有给出打表的程序(打表只需要把所有的答案都输出到出来,然后将答案存在一个数组中即可,这样就可以 0 m s 0ms 0ms通过本题了)。
代码1(直接dfs超时):
#include <bits/stdc++.h>
const int INF = 0x3f3f3f3f;
const int NUM_DIRECTION = 4;
using namespace std;
int n, w, h, ans;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
typedef set<pair<int, int>> Block;
Block block;
set<Block> occur;
// 将块移动到原点只需要让所有坐标都减去最小的坐标
void moveToOrigin(Block &block)
{
Block newBlock;
int minX = INF, minY = INF;
for (auto iter : block) {
minX = min(minX, iter.first);
minY = min(minY, iter.second);
}
for (auto iter : block) {
newBlock.insert({iter.first - minX, iter.second - minY});
}
block = newBlock;
}
// 沿着左上角的方块进行顺时针旋转
void clockwiseRotate(Block &block)
{
Block newBlock;
for (auto iter : block) {
newBlock.insert({iter.second, -iter.first});
}
block = newBlock;
moveToOrigin(block);
}
// 沿着最左边的方块进行左右翻转
void flipHorizontally(Block &oldBlock, Block &newBlock)
{
newBlock.clear();
for (auto iter : block) {
newBlock.insert({iter.first, -iter.second});
}
moveToOrigin(newBlock);
}
bool redundant(Block block)
{
moveToOrigin(block);
for (int i = 0; i < 4; i++) {
if (occur.count(block) == 1) { return true; }
clockwiseRotate(block);
}
Block newBlock;
flipHorizontally(block, newBlock);
for (int i = 0; i < 4; i++) {
if (occur.count(newBlock) == 1) { return true; }
clockwiseRotate(newBlock);
}
occur.insert(newBlock);
return false;
}
void dfs(int nowDepth)
{
if (nowDepth == n) {
if (!redundant(block)) {
ans++;
}
return;
}
if (nowDepth == 0) { // 第一个的位置任意
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
block.insert({i, j});
dfs(nowDepth + 1);
block.erase({i, j});
}
}
} else {
Block lasBlock = block;
for (auto iter : lasBlock) {
for (int k = 0; k < NUM_DIRECTION; k++) { // 选择已经放置的位置进行扩展
int nx = iter.first + dx[k];
int ny = iter.second + dy[k];
if (nx < 0 || nx >= h || ny < 0 || ny >= w || lasBlock.count({nx, ny}) == 1) { continue; }
block.insert({nx, ny});
dfs(nowDepth + 1);
block.erase({nx, ny});
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
while(cin >> n >> w >> h) {
ans = 0;
occur.clear();
block.clear();
dfs(0);
cout << ans << endl;
}
return 0;
}
代码2(提前计算出所有的不同数量的不同联通块):
#include <bits/stdc++.h>
const int INF = 0x3f3f3f3f;
const int NUM_DIRECTION = 4;
const int MAXN = 11;
const int MAXW = MAXN;
const int MAXH = MAXN;
using namespace std;
int n, w, h, ans;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
typedef set<pair<int, int>> Block;
Block block;
set<Block> occur[MAXN];
void moveToOrigin(Block &block); // 与代码1相同
void clockwiseRotate(Block &block); // 与代码1相同
void flipHorizontally(Block &oldBlock, Block &newBlock); // 与代码1相同
void redundant(Block block, int n)
{
moveToOrigin(block);
for (int i = 0; i < 4; i++) {
if (occur[n].count(block) == 1) { return; }
clockwiseRotate(block);
}
Block newBlock;
flipHorizontally(block, newBlock);
for (int i = 0; i < 4; i++) {
if (occur[n].count(newBlock) == 1) { return; }
clockwiseRotate(newBlock);
}
occur[n].insert(newBlock);
}
void init()
{
block.insert({0, 0});
redundant(block, 1);
for (int n = 2; n < MAXN; n++) {
for (Block lastBlock : occur[n - 1]) {
block = lastBlock;
for (auto iter : lastBlock) {
for (int i = 0; i < 4; i++) {
int nx = iter.first + dx[i];
int ny = iter.second + dy[i];
if (block.count({nx, ny}) == 1) { continue; }
block.insert({nx, ny});
redundant(block, n);
block.erase({nx, ny});
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
init();
while(cin >> n >> w >> h) {
ans = 0;
for (Block block : occur[n]) {
int maxX = 0, maxY = 0;
for (auto iter : block) {
maxX = max(maxX, iter.first);
maxY = max(maxY, iter.second);
}
if (min(maxX, maxY) < min(h, w) && max(maxX, maxY) < max(h, w)) { ans++; }
}
cout << ans << endl;
// 想要0ms通过只需要循环所有可能的n w h
// 然后计算出答案进行输出
// 最后将输出拷贝到一个数组中
// 这样对于输入只需要查询数组即可
}
return 0;
}