题意
给定一个 n∗ m 的网格,你在左下角 (n,1),你只能往前走或者右拐,障碍和走过的点不能走。求走到 (y,x) 的方案数 mod k 的值。
题别看错了
是指他能一直向右转圈圈,而不是只能向上,向右走。
比赛的时候一丁点想法都没有,dp能力有点小弱。
首先因为他是转着圈到终点的,我们反过来,从终点到起点,可以发现他实际上是一个慢慢变大的矩形。
这就是我们划分状态的依据,设
f[0..4][x][y][rx][ry]
f
[
0..4
]
[
x
]
[
y
]
[
r
x
]
[
r
y
]
表示当前考虑了左上角(x,y),右下角(rx,ry)这个矩形的
左上角(从右边走过来),
右上角(从上面走过来),
右下角(从左边走过来),
左下角(从上面走过来)的方案数。 也就是这个点一直在此矩形内运动。
现在考虑转移,因为状态数都是n^4的了,转移必须O(1)。
一个大矩形可以分解成两个小矩形的答案,就是这样:
再注意一下障碍和初值的问题,见标吧。
看不懂没关系,自己翻题解去吧!
接下来按照列数 + 行数的顺序转移,再滚动优化一下即可。
(这样实际上只需要存三个坐标即可。)
可能还要卡卡常数啥的
#include <cstdio>
#include <iostream>
#include <cstring>
#define dui(x,y) ((x) == ex && (y) == ey)
using namespace std;
typedef long long ll;
const int N = 110;
ll n,m,k,ex,ey;
ll o,f[2][4][N][N][N];
char e[N][N];
int has[N][N];
int xd[N][N];
int main() {
freopen("t2.in","r",stdin);
cin>>n>>m>>k>>ey>>ex;
for (int i = 1; i <= n; i++) {
scanf("%s",e[i] + 1);
for (int j = 1; j <= m; j++) {
has[i][j] = has[i][j-1] + (e[i][j] == '*');
xd[i][j] = xd[i-1][j] + (e[i][j] == '*');
}
}
for (int s = 2; s <= n + m; s++) {
// cout<<s - 1<<endl;
o = 1 - o;
// memset(f[o],0,sizeof f[o]);
for (int u = 1; u < s; u++) {
int v = s - u;
for (int x = 1; x <= ex && x + u - 1 <= n; x++) {
for (int y = 1; y <= ey && y + v - 1 <= m; y++) {
int rx = x + u - 1, ry = y + v - 1;
if (rx < ex || ry < ey) continue;
f[o][0][x][y][rx] = (f[1 - o][0][x][y][rx] + (has[x][ry] - has[x][y-1] == 0) * (f[1 - o][1][x+1][y][rx] + dui(x,ry))) % k;
f[o][1][x][y][rx] = (f[1 - o][1][x][y][rx - 1] + (xd[rx][ry] - xd[x-1][ry] == 0) * (f[1 - o][2][x][y][rx] + dui(rx,ry))) % k;
f[o][2][x][y][rx] = (f[1 - o][2][x][y + 1][rx] + (has[rx][ry] - has[rx][y-1] == 0) * (f[1 - o][3][x][y][rx - 1] + dui(rx,y))) % k;
f[o][3][x][y][rx] = (f[1 - o][3][x + 1][y][rx] + (xd[rx][y] - xd[x-1][y] == 0) * (f[1 - o][0][x][y + 1][rx] + dui(x,y))) % k;
}
}
}
}
printf("%lld\n",f[o][3][1][1][n]);
}