P3272 [SCOI2011] 地板 题解
题目
题意简述:有形如 \(L\) 的地板和一块矩形地面,在矩形地面上有些位置是 $* $ 有些位置是 $_ $,对于星星位置,不能铺地板。问有多少种铺地板的方案。其中对于每一个地板,必须要拐一个弯,即不能是一条直线。
分析
如果不会插头 DP 模板,请先学习再来看本题解,本题解默认读者都会插头 DP 的模板!
看到求方案数,我们很容易想到用 DP 来解决,再瞄一眼数据范围,容易想到是插头或者状压。状压不是很容易表示这道题的状态,所以我们选择使用插头 DP。
由于这道题中地板能且仅能拐一个弯,所以我们可以这样设计状态:
- 用 \(0\) 表示没有地板。
- 用 \(1\) 表示有地板,且这个地板没有拐过弯。
- 用 \(2\) 表示有地板,且这个地板已经拐过弯了。
所以我们的状态需要用三进制来存。由于四进制更快更好写,我们用四进制来写。
我们开始分类讨论:
为了方便表示,我们规定:\(i\) 表示行数,\(j\) 表示列数,且下标都是从 \(1\) 开始。\(x\) 表示左插头状态,\(y\) 表示右插头状态。\(g_{i,j}\) 表示是否能铺地板,其中 \(1\) 为可以铺地板。\(state\) 为当前状态表示,\(w\) 为当前状态的方案数。
- 当前 \(g_{i,j}\) 为 \(0\),那么这个格子不能有插头,我们继承上一个状态。即
insert (cur , state , w);
以下情况中 \(g_{i,j}\) 为 \(1\)。
- 当 \(x=0\) 且 \(y=0\) 时,我们有三种情况:
- 我们选择往下伸插头,那么我们
insert (cur , state+build(j-1 , 1) , w); - 我们选择往右伸插头,那么我们
insert (cur , state+build(j , 1) , w); - 我们两个方向都伸插头,那么我们
insert (cur , state+build(j-1 , 2)+build(j , 2) , w);
- 我们选择往下伸插头,那么我们
- 当 \(x=0\) 且 \(y=1\) 时,我们有两种情况:
- 我们选择往下延伸上插头,那么我们
insert (cur , state+build(j-1 , 1)-build(j , 1) , w); - 我们选择往右延伸,也就是拐个弯,那么我们要把状态从 \(1\) 转换成 \(2\)。即
insert (cur , state+build(j , 1) , w);
- 我们选择往下延伸上插头,那么我们
- 当 \(x=1\) 且 \(y=0\) 时,同情况三。
- 当 \(x=1\) 且 \(y=1\) 时,也就是说两个没有拐过弯的插头相遇了!他们会接在一起,变成一个拐弯的。注意,如果这个格子是最后一个可以铺地板的格子时,我们需要更新答案。即:
if (end_x==i && end_y==j)
{
res = (res+w)%mod;
}
insert (cur , state-build(j-1 , 1)-build(j , 1) , w);
- 当 \(x=2\) 且 \(y=0\) 时,左插头是拐了弯的地板,那要么就这样终止左插头对应的地板,要么往右延伸。同时,如果这个格子是最后一个铺地板的格子,我们也要更新答案。即:
if (end_x==i && end_y==j)
{
res = (res+w)%mod;
}
if (j+1<=m && g[i][j+1])
{
insert (cur , state-build(j-1 , 2)+build(j , 2) , w);
}
insert (cur , state-build(j-1 , 2) , w);
- 当 \(x=0\) 且 \(y=2\) 时,同情况六。
- 当 \(x=2\) 且 \(y=1\) 时,左插头对应的地板已经拐过弯了,而上插头也需要插进来,这样会冲突,所以这种情况不合法。
- 当 \(x=1\) 且 \(y=2\) 时,同上。
- 当 \(x=2\) 且 \(y=2\) 时,同上。
代码
稍微有点细节:
- 进行状态转移时,转到的新格子是可以铺地板的。如上文情况三中往下延伸插头,那么 \(g_{i+1,j}\) 是一个能铺地板的格子,不然不能转移。
- 进行状态转移时,因为需要判断是否能铺,所以可能会造成数组越界,记得不能让数组越界了。
- 由于我们插头 DP 是以行来枚举的,但是极限情况下可能是一个长为 \(50\) 宽为 \(2\) 的矩形,那么我们状态就要存十进制下一百位,这个大小会爆掉。其次这样状态数会很多很多,时间复杂度上也没有保证,所以我们需要交换一下 \(r\) 和 \(c\),并且把矩形旋转九十度。
下面放出代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 , M = N*2+7 , mod = 20110520;
int n , m; //用n和m来代替r和c
int g[110][110] , g1[110][110];
int q[2][N] , cnt[2];
int v[2][M] , h[2][M];
int end_x , end_y;
int find (int cur , int state) //哈希表找位置
{
int t = state%M;
while (h[cur][t]!=-1 && h[cur][t]!=state)
{
if (++t==M) t = 0;
}
return t;
}
void insert (int cur , int state , int w) //哈希表插入
{
int t = find (cur , state);
if (h[cur][t]==-1)
{
h[cur][t] = state , v[cur][t] = w;
q[cur][++cnt[cur]] = t;
}
else
{
v[cur][t] = (v[cur][t]+w)%mod;
}
}
int get (int state , int k) //找到第k位
{
return (state>>(k*2))&3;
}
int build (int k , int x) //构造出第k位值为x的数
{
return x*(1<<(k*2));
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
cin >> n >> m;
for (int i=1 ; i<=n ; i++)
{
string s;
cin >> s;
for (unsigned int j=0 ; j<s.size() ; j++)
{
if (s[j]=='_')
{
g1[i][j+1] = 1;
g[i][j+1] = 1;
end_x = i , end_y = j+1;
}
}
}
if (end_x==0 && end_y==0) //特判全是不能选的
{
cout << "1\n";
return 0;
}
if (n<m) //旋转矩形,交换n和m
{
for (int i=1 ; i<=m ; i++)
{
for (int j=1 ; j<=n ; j++)
{
g[i][j] = g1[j][i];
}
}
swap (n , m) , swap (end_x , end_y);
}
int res = 0;
memset (h , -1 , sizeof h);
int cur = 0;
insert (cur , 0 , 1);
for (int i=1 ; i<=n ; i++)
{
for (int j=1 ; j<=cnt[cur] ; j++)//换行
{
h[cur][q[cur][j]]<<=2;
}
for (int j=1 ; j<=m ; j++)
{
int last = cur;
cur ^= 1 , cnt[cur] = 0;
memset (h[cur] , -1 , sizeof h[cur]);
for (int k=1 ; k<=cnt[last] ; k++)
{
int state = h[last][q[last][k]] , w = v[last][q[last][k]];
int x = get (state , j-1) , y = get (state , j);
if (!g[i][j]) //情况1
{
insert (cur , state , w);
}
else if (x==0 && y==1) //情况3
{
if (i+1<=n && g[i+1][j])
{
insert (cur , state+build(j-1 , 1)-build(j , 1) , w);
}
if (j+1<=m && g[i][j+1])
{
insert (cur , state+build(j , 1) , w);
}
}
else if (x==1 && y==0) //情况4
{
if (j+1<=m && g[i][j+1])
{
insert (cur , state-build(j-1 , 1)+build(j , 1) , w);
}
if (i+1<=n && g[i+1][j])
{
insert (cur , state+build(j-1 , 1) , w);
}
}
else if (x==1 && y==1) //情况5
{
if (end_x==i && end_y==j)
{
res = (res+w)%mod;
}
insert (cur , state-build(j-1 , 1)-build(j , 1) , w);
}
else if (x==2 && y==0) //情况6
{
if (end_x==i && end_y==j)
{
res = (res+w)%mod;
}
if (j+1<=m && g[i][j+1])
{
insert (cur , state-build(j-1 , 2)+build(j , 2) , w);
}
insert (cur , state-build(j-1 , 2) , w);
}
else if (x==0 && y==2) //情况7
{
if (end_x==i && end_y==j)
{
res = (res+w)%mod;
}
if (i+1<=n && g[i+1][j])
{
insert (cur , state-build(j , 2)+build(j-1 , 2) , w);
}
insert (cur , state-build(j , 2) , w);
}
else if (x==0 && y==0) //情况2
{
if (j+1<=m && g[i][j+1])
{
insert (cur , state+build(j , 1) , w);
}
if (i+1<=n && g[i+1][j])
{
insert (cur , state+build(j-1 , 1) , w);
}
if (i+1<=n && j+1<=m && g[i+1][j] && g[i][j+1])
{
insert (cur , state+build(j-1 , 2)+build(j , 2) , w);
}
}
}
}
}
cout << res << '\n';
return 0;
}

浙公网安备 33010602011771号