[UVA1602]Lattice Animals

本文探讨了一道关于计算包含特定数量格子的四连通块数量的问题,通过观察输入限制和剪枝策略,利用Burnside定理分析了如何快速判断元素的等价性,展示了代码实现并揭示了关键的算法优化。

题目

传送门 to UVA

题目描述
有多少个本质不同的四连通块,恰包含 n n n 个格子,且能够放入 h × w h\times w h×w 的矩形?如果两个四连通块可以通过翻转、旋转、平移变得相同,那么二者本质相同。

数据范围与提示
max ⁡ ( n , h , w ) ⩽ 10 \max(n,h,w)\leqslant 10 max(n,h,w)10

思路

塔木德,这题神了!

发现一共只有 1 0 3 10^3 103 种不同的输入,并且题目看上去不可做的样子,肯定考虑打表。不过 ( 100 10 ) {100\choose 10} (10100) 也搜不出来啊……

此时,我旁边坐着一条吃得翔中翔,方为狗上狗!它说:“汪汪,汪旺妄,往往忘网,枉王!”(由 G o o g l a \rm Googla Googla 提供狗语翻译:其实它很快答案就是零了!)

什么叫 “很快” ?就是 h + w > n + 1 h+w>n+1 h+w>n+1 的时候。当然这里需要 恰好占用一个 h × w h\times w h×w 的子矩形。更强的限制条件,只会让搜索更快,这就是剪枝简单而强大的原理!

所以实际上只会是 5 × 6 5\times 6 5×6 4 × 7 4\times 7 4×7 等的子矩形。那么只有最多 ( 35 10 ) {35\choose 10} (1035) 个情况。多数情况下它还不会形成连通块,其实非常快!

当然,怎么判断本质相同?就用 B u r n s i d e \rm Burnside Burnside 定理 也是可以的。注意恰好占用 h × w h\times w h×w 的子矩形,和恰好占用 w × h w\times h w×h 的子矩形,二者之间是本质相同的。做 B u r n s i d e \rm Burnside Burnside 要注意,一定要构成群,所以要二者一起考虑。最后统计答案的时候,也只加其中之一。

具体怎么打呢?注意到旋转和翻转本质都是对向量 ( x , y ) (x,y) (x,y) 做线性变换。要么是在某一维加负号,要么是交换两个维度,并在其中一维添符号。所以最终 ( x , y ) (x,y) (x,y) 一定得到 ( ± x , ± y ) (\pm x,\pm y) (±x,±y) ( ± y , ± x ) (\pm y,\pm x) (±y,±x)

注意还有平移。平移不改变相对位置关系。找到原图形的最靠下、最靠左的一个。那么根据变换规则,很容易得到它应该对应到新图形的哪个点(最靠左或右,最靠上或下),就推出了变换后的平移量。

试了试,只跑了 5 s 5s 5s 左右就可以输出整张表。不可思议!

代码

当然是给出打表程序。注意双重循环用一个 b r e a k \tt break break 不能弹出干净!

如果 h = w h=w h=w,那就是有 8 8 8 种置换;否则要先乘 2 2 2,得到 f ( w , h ) + f ( h , w ) f(w,h)+f(h,w) f(w,h)+f(h,w) 的值,再除以 8 8 8

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int_ readint(){
	int_ a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 10;
int a[MaxN][MaxN], w, h, n;

int ans, mat[2][2], cnt;
bool vis[MaxN][MaxN];
void dfs(int x,int y){
	if(x < 0 || x >= w) return ;
	if(y < 0 || y >= h) return ;
	if(!a[x][y] || vis[x][y]) return ;
	vis[x][y] = true, ++ cnt;
	dfs(x,y-1), dfs(x,y+1);
	dfs(x-1,y), dfs(x+1,y);
}
bool checkMove(int tx,int ty){
	int vx = mat[0][0]+mat[1][0], dx = 0;
	int vy = mat[0][1]+mat[1][1], dy = 0;
	int frx = 0, tox = w; // [from,to)
	if(vx == -1) // (-y,?) or (-x,?)
		frx = w-1, tox = -1;
	int fry = 0, toy = h;
	if(vy == -1) // (?,-x) or (?,-y)
		fry = h-1, toy = -1;
	if(mat[0][0]){ // no swapping
		for(int i=frx; i!=tox; i+=vx)
		for(int j=fry; j!=toy; j+=vy){
			if(!a[i][j]) continue;
			dx = i-(tx*mat[0][0]);
			dy = j-(ty*mat[1][1]);
			goto FUCK_DOUBLE_FOR_LOOP;
		}
	}
	else{ // swapped coordinate
		for(int j=fry; j!=toy; j+=vy)
		for(int i=frx; i!=tox; i+=vx){
			if(!a[i][j]) continue;
			dx = i-(ty*mat[1][0]);
			dy = j-(tx*mat[0][1]);
			goto FUCK_DOUBLE_FOR_LOOP;
		}
	}
	FUCK_DOUBLE_FOR_LOOP:
	rep(i,0,w-1) rep(j,0,h-1){
		if(!a[i][j]) continue;
		int I = i*mat[0][0]+j*mat[1][0]+dx;
		int J = i*mat[0][1]+j*mat[1][1]+dy;
		if(I < 0 or I >= w or
		J < 0 or J >= h or a[I][J] == 0)
			return false;
	}
	return true;
}
void check(){
	bool f_l = false, f_r = false;
	rep(i,0,w-1){
		memset(vis[i],0,h);
		if(a[i][0]) f_l = true;
		if(a[i][h-1]) f_r = 1;
	}
	if(!f_l || !f_r) return ;
	rep(j,f_r=0,h-1)
		if(a[w-1][j]) f_r = 1;
	if(!f_r) return ;
	int tx, ty; // save position
	rep(j,f_l=0,h-1){
		if(!a[0][j]) continue;
		f_l = 1, cnt = 0;
		dfs(tx = 0, ty = j);
		if(cnt != n) return ;
		else break;
	}
	if(!f_l) return ; // not upmost
	rep(i,0,1) rep(j,0,1) rep(k,0,1){
		mat[0][i] = 1-(j<<1);
		mat[1][i^1] = 1-(k<<1);
		mat[0][i^1] = mat[1][i] = 0;
		ans += checkMove(tx,ty);
	}
}
void solve(int t=0,int cnt=0){
	if(cnt == n) return check();
	if(t == h && !cnt) return ; // pre-check
	if(t == h*w) return ;
	a[t/h][t%h] = 1, solve(t+1,cnt+1);
	a[t/h][t%h] = 0, solve(t+1,cnt);
}

int main(){
	// freopen("AC.cpp","w",stdout);
	for(n=1; n<=10; ++n)
	for(h=1; h<=n; ++h)
	for(w=h; w<=n&&h+w<=n+1; ++w){
		ans = 0; solve();
		if(!ans) continue;
		printf("dp[%d][%d][%d] = %d;\n",n,h,w,ans>>2>>(h==w));
	}
	return 0;
}

以及注意答案输出的时候,不妨设 w ⩽ h w\leqslant h wh,那么只考虑 w ′ ⩽ h ′ w'\leqslant h' wh 的情况。核心代码见下。

	if(w > h) swap(w,h);
	rep(i,1,w) rep(j,i,h)
		ans += dp[n][i][j];
	printf("%d\n",ans);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值