題目描述
有一個n行m列的整數矩陣,其中1到nm之間的每個整數恰好出現一次。如果一個格子比所有相鄰格子(相鄰是指有公共邊或公共頂點)都小,我們說這個格子是區域性極小值。
給出所有區域性極小值的位置,你的任務是判斷有多少個可能的矩陣。
輸入
輸入第一行包含兩個整數n和m(1<=n<=4, 1<=m<=7),即行數和列數。以下n行每行m個字元,其中“X”表示區域性極小值,“.”表示非區域性極小值。
輸出
輸出僅一行,為可能的矩陣總數除以12345678的餘數。
樣例輸入
3 2
X.
..
.X
樣例輸出
60
題解
容斥原理+狀壓dp
“給出所有區域性極小值的位置” 有兩層含義:
1.給出的位置是區域性最小值;
2.非給出的位置不是區域性最小值。
先考慮第一層含義怎麼做:
我們把數從小到大填入矩陣中,那麼如果一個格子是區域性最小值且沒有填入數,那麼它周圍的數都不能填。除此之外的位置均可選擇。
因此設 $f[i][j]$ 表示填入前 $i$ 個數,區域性最小值的填入情況為 $j$ 的方案數。
那麼對於 $f[i][j]$ ,有兩種轉移:
不填區域性最小值的位置,那麼 $f[i][j]=f[i-1][j]+可以填的位置數$ 。我們預處理每種區域性最小值填入情況下可以填入多少個數 $v[j]$,之後就能算出可以填的位置數 $v[j]-i+1$ 。
填區域性最小值的位置,那麼列舉填入了第 $k$ 個區域性最小值,有 $f[i][j]=f[i-1][j-2^k]$ 。
最終 $f[nm][2^{區域性最小值個數}-1]$ 即為答案。
再考慮第二層含義:
考慮容斥,那麼討論其它位置為區域性最小值的情況,同樣的方法進行dp,乘以容斥係數 $(-1)^{多填的位置數}$ 累計到答案中即可。
注意判斷無解的情況。
由於容斥過程時刻要求一個區域性最小值的八連通位置不能存在區域性最小值,因此狀態數是很小的。
時間複雜度 $O(跑得飛快)$
#include <cstdio>
#include <cstring>
#include <algorithm>
#define mod 12345678
using namespace std;
const int dx[] = {0 , 1 , 1 , 1 , 0 , -1 , -1 , -1 , 0} , dy[] = {0 , 1 , 0 , -1 , -1 , -1 , 0 , 1 , 1};
char str[10];
int n , m , px[30] , py[30] , p , vis[6][10] , filled[6][10] , v[260] , f[30][260];
int solve()
{
int i , j , k;
memset(v , 0 , sizeof(v));
for(i = 0 ; i < (1 << p) ; i ++ )
{
memset(vis , 0 , sizeof(vis));
for(j = 0 ; j < p ; j ++ )
if(!(i & (1 << j)))
for(k = 0 ; k < 9 ; k ++ )
vis[px[j] + dx[k]][py[j] + dy[k]] = 1;
for(j = 1 ; j <= n ; j ++ )
for(k = 1 ; k <= m ; k ++ )
v[i] += !vis[j][k];
}
memset(f , 0 , sizeof(f));
f[0][0] = 1;
for(i = 1 ; i <= n * m ; i ++ )
{
for(j = 0 ; j < (1 << p) ; j ++ )
{
if(v[j] >= i) f[i][j] = f[i - 1][j] * (v[j] - i + 1) % mod;
for(k = 0 ; k < p ; k ++ )
if(j & (1 << k))
f[i][j] = (f[i][j] + f[i - 1][j ^ (1 << k)]) % mod;
}
}
return f[n * m][(1 << p) - 1];
}
int dfs(int x , int y)
{
if(y > m) x ++ , y = 1;
if(x > n) return solve();
int i , ans = dfs(x , y + 1);
for(i = 0 ; i < 9 ; i ++ )
if(filled[x + dx[i]][y + dy[i]])
break;
if(i == 9)
{
px[p] = x , py[p ++ ] = y , filled[x][y] = 1;
ans = (ans - dfs(x , y + 1) + mod) % mod;
p -- , filled[x][y] = 0;
}
return ans;
}
int main()
{
int i , j;
scanf("%d%d" , &n , &m);
for(i = 1 ; i <= n ; i ++ )
{
scanf("%s" , str + 1);
for(j = 1 ; j <= m ; j ++ )
if(str[j] == 'X')
px[p] = i , py[p ++ ] = j;
}
for(i = 0 ; i < p ; i ++ )
{
for(j = 0 ; j < i ; j ++ )
{
if(abs(px[i] - px[j]) <= 1 && abs(py[i] - py[j]) <= 1)
{
puts("0");
return 0;
}
}
filled[px[i]][py[i]] = 1;
}
printf("%d\n" , dfs(1 , 1));
return 0;
}