Luogu-P9743-旅行

题目 P9743 「KDOI-06-J」旅行

题目描述

小 C 在 C 国旅行。

C 国有 n×mn\times m 个城市,可以看做 n×mn\times m 的网格。定义 (i,j)(i,j) 表示在网格中第 ii 行第 jj 列的城市。

该国有 22 种交通系统:

  • 对于所有 1i<n,1jm1\leq i<n,1\leq j\leq m(i,j)(i,j)(i+1,j)(i+1,j) 有一段由 L 公司修的单向铁路;
  • 对于所有 1in,1j<m1\leq i\leq n,1\leq j<m(i,j)(i,j)(i,j+1)(i,j+1) 有一段由 Z 公司修的单向铁路;

在每一个城市有一个售票口,(i,j)(i,j) 城市的售票口可以用 ai,ja_{i,j} 元购买一张 L 公司的铁路票,bi,jb_{i,j} 元购买一张 Z 公司的铁路票。当你拥有一个公司的一张铁路票时,你可以乘坐这个公司的任意一段铁路,并消耗掉这张铁路票。注意,一张铁路票可以且仅可以使用一次。

小 C 原来在城市 (1,1)(1,1)。他想要在 C 国旅游,但是他不想浪费任何的钱(即,当他旅游完毕时手上不应该有多余的车票)。对于所有 1xn,1ym1\leq x\leq n,1\leq y\leq m,求他花 kk 元钱并在城市 (x,y)(x,y) 结束旅行的方案数,对 998 244 353998\ 244\ 353 取模。

两种旅行方案不同,当且仅当小 C 经过的城市不同,或他在某一个城市购买的某家公司的铁路票数量不同。

输入格式

从标准输入读入数据。

输入的第一行包含三个正整数 n,m,kn,m,k,表示网格的大小和钱的数目。

接下来 nn 行,每行 mm 个正整数,第 ii 行第 jj 个正整数表示 ai,ja_{i,j}

接下来 nn 行,每行 mm 个正整数,第 ii 行第 jj 个正整数表示 bi,jb_{i,j}

输出格式

输出到标准输出。

输出一共 nn 行,每行 mm 个整数,表示到每个点钱恰好花完并结束旅行的方案数,对 998 244 353998\ 244\ 353 取模。

思路:dp

dp表

注意到本题对于一格共有 55 个变量:

  • 横坐标 xx
  • 纵坐标 yy
  • 横着走的车票数 sxsx
  • 竖着走的车票数 sysy
  • 剩余钱数 moneymoney

并且根据本题的数据范围,考虑五维的dp表 dp[x][y][sx][sy][money]dp[x][y][sx][sy][money]

状态

  • 初状态:由于初始位置是 (1,1)(1,1),此时两种车票都没有,剩余钱数为题中的 kk,因此以下方程恒成立。

    1
    dp[1][1][0][0][k] = 1;
  • 状态转移方程:dp[x][y][sx][sy][money]dp[x][y][sx][sy][money] 为所有能变化成这种状态的方案数总和。

    而根据题意,存在以下状态可以变成 dp[x][y][sx][sy][money]dp[x][y][sx][sy][money]

    • (x1,y)(x-1, y),花费一张横向车票。dp[x1][y][sx+1][sy][money]\rightarrow dp[x-1][y][sx+1][sy][money]
    • (x,y1)(x, y-1),花费一张纵向车票。dp[x][y1][sx][sy+1][money]\rightarrow dp[x][y-1][sx][sy+1][money]
    • 原地花 a[x][y]a[x][y] 的钱买一张横向车票。dp[x][y][sx1][sy][money+a[x][y]]\rightarrow dp[x][y][sx-1][sy][money+a[x][y]]
    • 原地花 b[x][y]b[x][y] 的钱买一张纵向车票。dp[x][y][sx][sy1][money+b[x][y]]\rightarrow dp[x][y][sx][sy-1][money+b[x][y]]

    因此,dp[x][y][sx][sy][money]dp[x][y][sx][sy][money] 应为以上四个式子的和(注意取模)。

    1
    2
    3
    4
    5
    dp[x][y][sx][sy][money] += 
    dp[x - 1][y][sx + 1][sy][money] +
    dp[x][y - 1][sx][sy + 1][money] +
    dp[x][y][sx - 1][sy][money + a[x][y]] +
    dp[x][y][sx][sy - 1][money + b[x][y]];

    需要注意,以上方程并不正确

    因为根据题意,购买车票的顺序不同是不算方案不同的,例:先买 11 横向再买 11 纵向,和先买 11 纵向再买 11 横向不应算两种方案。

    因此,还应该在同时买两种车票(sx0,sy0sx \neq 0 , sy \neq 0)时,去除重复部分 dp[i][y][sx1][sy1][money+a[x][y]+b[x][y]]dp[i][y][sx-1][sy-1][money+a[x][y]+b[x][y]]

  • 末状态:题目要求到达的点不能有票(sx=sy=0sx=sy=0)和剩下的钱(money=0money=0),因此到达 (x,y)(x,y) 的方案数为:

    1
    dp[x][y][0][0][0];

遍历的范围

  • xxyy 自然是 [1,n][1,n][1,m][1,m]

  • sxsx 可以这样考虑:当横坐标为 xx 时,最多可拥有 nxn-x 张横向的票,因为再多也用不上了。

    所以 sxsx 的范围是 [0,nx][0,n-x]sysy 同理:[0,my][0,m-y]

  • moneymoney 比较特殊。虽然范围很容易想到是 [0,k][0,k],但是方程中是由较大的钱数计算较小的钱数,所以要倒序循环。

空间压缩:滚动数组

此时,虽然看起来十分完美,但是数组太大了,会MLE。

注意到:当横坐标为 xx 时的方案数只与 xxx1x-1 有关,所以使用滚动数组方法将第一维压缩成 22 而不是 nn

方法

使用一个变量 ii 存储 xx的奇偶,每次循环将 ii 置为 1 xor i1 \ \mathrm{xor}\ i

压缩后状态

  • 初状态不变。

  • 状态转移方程中所有的 xx 换成 iix1x-1 换成 1 xor i1 \ \mathrm{xor} \ i(另一个数组就是上一个 xx)。

  • 末状态的处理有一些变化。因为 dpdp 数组不再存储每个 (x,y)(x,y) 的方案数,所以应在计算完之后存到另一个数组 ansans 里。

AC Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// template v4
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int inf = 0x7fffffff;

const int N = 50;
const int M = 100;

int dp[2][N][M][M][M];

int A[N][N];
int B[N][N];

int ans[N][N];

const int MOD = 998244353;

int main() {

int n, m, k;
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> A[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> B[i][j];
}
}
dp[1][1][0][0][k] = 1;
int i = 0;
for(int x = 1; x <= n; x++){
i ^= 1;
for(int y = 1; y <= m; y++){
for(int money = k; money >= 0; money--){
for(int sx = 0; sx + x <= n; sx++){
for(int sy = 0; sy + y <= m; sy++){
ll res = 0;
if(x != 1) res += dp[i ^ 1][y][sx + 1][sy][money];
if(y != 1) res += dp[i][y - 1][sx][sy + 1][money];
res %= MOD;
if(sx && A[x][y] + money <= k) res += dp[i][y][sx - 1][sy][money+A[x][y]];
if(sy && B[x][y] + money <= k) res += dp[i][y][sx][sy - 1][money+B[x][y]];
if(sx && sy && A[x][y] + B[x][y] + money <= k) res += MOD - dp[i][y][sx - 1][sy - 1][money + A[x][y] + B[x][y]];
dp[i][y][sx][sy][money] += (res % MOD);
dp[i][y][sx][sy][money] %= MOD;
}
}
}
ans[x][y] = dp[i][y][0][0][0];
}
memset(dp[i ^ 1], 0, sizeof dp[i ^ 1]);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cout << ans[i][j] << ' ';
}
cout << endl;
}
return 0;
}