题目
题目描述
有多少个本质不同的四连通块,恰包含
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 w⩽h,那么只考虑 w ′ ⩽ h ′ w'\leqslant h' w′⩽h′ 的情况。核心代码见下。
if(w > h) swap(w,h);
rep(i,1,w) rep(j,i,h)
ans += dp[n][i][j];
printf("%d\n",ans);