UVa 1602 Lattice Animals

Lattice animal is a set of connected sites on a lattice. Lattice animals on a square lattice are especially popular subject of study and are also known as polyominoes. Polyomino is usually represented as a set of sidewise connected squares. Polyomino with n squares is called n-polyomino. In this problem you are to find a number of distinct free n-polyominoes that fit into rectangle w×h. Free polyominoes can be rotated and flipped over, so that their rotations and mirror images are considered to be the same. For example, there are 5 different pentominoes (5-polyominoes) that fit into 2×4 rectangle and 3 different octominoes (8-polyominoes) that fit into 3×3 rectangle.
\epsfbox{p3224.eps}

Input 

The input file contains several test cases, one per line. This line consists of 3 integer numbers nw, and h ( 1$ \le$n$ \le$101$ \le$wh$ \le$n).

Output 

For each one of the test cases, write to the output file a single line with a integer number -- the number of distinct free n-polyominoes that fit into rectangle w×h.

Sample Input 

5 1 4
5 2 4
5 3 4
5 5 5
8 3 3

Sample Output 

0
5
11
12
3

#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
#define maxn 10
// 代表一个网格节点
typedef struct cell
{
	int x, y;	//网格节点的坐标 

	// 构造函数
	cell(int x, int y)
	{
		this->x = x;
		this->y = y;
	}

	bool operator < (const struct cell& a) const
	{
		return x < a.x || (x == a.x && y < a.y);
	}
}cell;

// 一个Polyomino就是一堆cell的集合
typedef set<cell> poly;

// poly_set[i]代表有i个cell的poly集合
set<poly> poly_set[maxn+1];

// answer[n][w][h]的答案 
int answer[maxn+1][maxn+1][maxn+1];

void gen_poly();
void check_poly(const poly& this_p, cell& this_c);
poly normalize(poly& p);
poly rotate(poly& p);
poly flip(poly& p);


int main()
{
	// 生成所有poly
	gen_poly();
	
//	printf("here\n");
	int n, w, h;
	while(scanf("%d %d %d", &n, &w, &h) == 3)
	{
		printf("%d\n", answer[n][w][h]);	
	}	
	return 0;
}

int dic_x[4] = {-1,0,1,0};
int dic_y[4] = {0,1,0,-1};

// 生成所有poly
void gen_poly()
{
	for(int i = 1; i <= maxn; i++)
		poly_set[i] = set<poly>();

	// 先生成有1个cell的poly
	poly p1;
	p1.insert(cell(0,0));
	poly_set[1].insert(p1);

	// 分别根据有i-1个cell的poly集合来生成有i个cell的poly集合
	for(int i = 2; i <= maxn; i++)
	{
		// 对每个poly中的每个cell尝试在不同的四个方向增加一个cell
		for(set<poly>::iterator p = poly_set[i-1].begin(); p != poly_set[i-1].end(); p++)
		{
			for(poly::const_iterator q = p->begin(); q != p->end(); q++)
			{
				for(int j = 0; j < 4; j++)
				{
					cell new_c(q->x+dic_x[j], q->y+dic_y[j]);
//					cell new_c;
					if(p->find(new_c) == p->end())
					{
						// 检查形成的这个poly是否存在,如果不存在就加入
						check_poly(*p, new_c);
					}			
			
				}
			}	
		}	
	}

	// 对所有n,w,h生成答案
	for(int i = 1; i <= maxn; i++)
	{
		for(int w = 1; w <= i; w++)
		{
			for(int h = 1; h <= i; h++)
			{
				int count = 0;
				for(set<poly>::iterator p = poly_set[i].begin(); p != poly_set[i].end(); p++)
                		{
					int max_x = p->begin()->x, max_y = p->begin()->y;
					for(poly::iterator q = p->begin(); q != p->end(); q++)
					{
						if(max_x < q->x)
							max_x = q->x;
						if(max_y < q->y)
							max_y = q->y;
					}
					
					if(min(max_x, max_y) < min(w, h) && max(max_x, max_y) < max(w, h))
					{
						count++;	
					}	
				}
/*				if(count != 0)
					printf("answer[%d][%d][%d] = %d\n", i, w, h, count);
*/				answer[i][w][h] = count;	
			}
		}
	}								
}


// 检查形成的这个poly加上这个cell是否存在,如果不存在就加入
void check_poly(const poly& this_p, cell& this_c)
{
	poly p = this_p;
	p.insert(this_c);
	// 规范化到最小点为(0,0)
	p = normalize(p);

	int n = p.size();
	// 检查旋转的8个方向是否存在,如果不存在就加入到poly集合
	for(int i = 0; i < 4; i++)
	{
		if(poly_set[n].find(p) != poly_set[n].end())
			return;
		// 对该poly向右旋转90度
		p = rotate(p);		
	}
	// 将该poly向下反转180度
	p = flip(p);
	for(int i = 0; i < 4; i++)
        {
                if(poly_set[n].find(p) != poly_set[n].end())
                        return;
                // 对该poly向右旋转90度
                p = rotate(p);
        }
	poly_set[n].insert(p);
				
}

// 规范化到最小点为(0,0)
poly normalize(poly& p)
{
	poly this_p;
	int min_x = p.begin()->x, min_y = p.begin()->y;
	for(poly::iterator q = p.begin(); q != p.end(); q++)
	{
		if(q->x < min_x)
			min_x = q->x;
		if(q->y < min_y)
			min_y = q->y;	
	}
	for(poly::iterator q = p.begin(); q != p.end(); q++)    
        {
		this_p.insert(cell(q->x-min_x,q->y-min_y));	
        }		
	return this_p;	
}

// 对该poly向右旋转90度
poly rotate(poly& p)
{
	poly this_p;
	for(poly::iterator q = p.begin(); q != p.end(); q++)
        {
                this_p.insert(cell(q->y,-q->x));
        }
        return normalize(this_p);	
}


// 将该poly向下反转180度
poly flip(poly& p)
{
	poly this_p;
        for(poly::iterator q = p.begin(); q != p.end(); q++)
        {
                this_p.insert(cell(q->x,-q->y));
        }
        return normalize(this_p);
}

枚举所有的n连块,难点在于如何枚举,没有想出来,后来参考https://github.com/aoapc-book/aoapc-bac2nd/blob/master/ch7/UVa1602.cpp,自己实现了一遍。
难点:
1.以每个格子来扩展。先枚举1连块,在对1连块的每个格子的4个方向进行扩展,枚举2连块,依次类推。
2.将n连块表示成n个格子的集合,将所有的n连块又表示成集合,判重任务交给set.
3.判重时要将n连块进行8个方向的旋转,并且每个n连块需要规范化(左下角的格子在(0,0)).
4.得到n连块后判断是否能放进w*h的网格中,由于n连块已经规范化,得到n连块的格子最大x,y坐标,即能盛下该n连块的长和宽。


                
这是一道比较经典的计数问题。题目描述如下: 给定一个 $n \times n$ 的网格图,其中一些格子被标记为障碍。一个连通块是指一些被标记为障碍的格子的集合,满足这些格子在网格图中连通。一个格子是连通的当且仅当它与另一个被标记为障碍的格子在网格图中有公共边。 现在,你需要计算在这个网格图中,有多少个不同的连通块,满足这个连通块的大小(即包含的格子数)恰好为 $k$。 这是一道比较经典的计数问题,一般可以通过计算生成函数的方法来解决。具体来说,我们可以定义一个生成函数 $F(x)$,其中 $[x^k]F(x)$ 表示大小为 $k$ 的连通块的个数。那么,我们可以考虑如何计算这个生成函数。 对于一个大小为 $k$ 的连通块,我们可以考虑它的形状。具体来说,我们可以考虑以该连通块的最左边、最上边的格子为起点,从上到下、从左到右遍历该连通块,把每个格子在该连通块中的相对位置记录下来。由于该连通块的大小为 $k$,因此这些相对位置一定是 $(x,y) \in [0,n-1]^2$ 中的 $k$ 个不同点。 现在,我们需要考虑如何计算这些点对应的连通块是否合法。具体来说,我们可以考虑从左到右、从上到下依次处理这些点,对于每个点 $(x,y)$,我们需要考虑它是否能够与左边的点和上边的点连通。具体来说,如果 $(x-1,y)$ 和 $(x,y)$ 都在该连通块中且它们在网格图中有公共边,那么它们就是连通的;同样,如果 $(x,y-1)$ 和 $(x,y)$ 都在该连通块中且它们在网格图中有公共边,那么它们也是连通的。如果 $(x,y)$ 与左边和上边的点都不连通,那么说明这个点不属于该连通块。 考虑到每个点最多只有两个方向需要检查,因此时间复杂度为 $O(n^2 k)$。不过,我们可以使用类似于矩阵乘法的思想,将这个过程优化到 $O(k^3)$ 的时间复杂度。 具体来说,我们可以设 $f_{i,j,k}$ 表示状态 $(i,j)$ 所代表的点在连通块中,且连通块的大小为 $k$ 的方案数。显然,对于一个合法的 $(i,j,k)$,我们可以考虑 $(i-1,j,k-1)$ 和 $(i,j-1,k-1)$ 这两个状态,然后把点 $(i,j)$ 加入到它们所代表的连通块中。因此,我们可以设计一个 $O(k^3)$ 的 DP 状态转移,计算 $f_{i,j,k}$。 具体来说,我们可以考虑枚举连通块所包含的最右边和最下边的格子的坐标 $(x,y)$,然后计算 $f_{x,y,k}$。对于一个合法的 $(x,y,k)$,我们可以考虑将 $(x,y)$ 所代表的点加入到 $(x-1,y,k-1)$ 和 $(x,y-1,k-1)$ 所代表的连通块中。不过,这里需要注意一个细节:如果 $(x-1,y)$ 和 $(x,y)$ 在网格图中没有相邻边,那么它们不能算作连通的。因此,我们需要特判这个情况。 最终,$f_{n,n,k}$ 就是大小为 $k$ 的连通块的个数,时间复杂度为 $O(n^2 k + k^3)$。 参考代码:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值