[BZOJ3111][Zjoi2013]蚂蚁寻路(DP)

orz DP 。

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=3111

Solution

画个图可以发现是长城一样的形状。具体是:
2K+1 2 K + 1 个非空矩形顺次横向相接,这些矩形底部所处的行号相等。
对于第 i(i>1) i ( i > 1 )
如果 i i 是奇数,那么第 i 个矩形严格高于第 i1 i − 1 个矩形。
否则 i i 是偶数,那么第 i 个矩形严格低于第 i1 i − 1 个矩形。
枚举封闭图形最后一行所处的行号(记为 rw r w ):
定义状态: f[i][j][k] f [ i ] [ j ] [ k ] 表示由 k k 个矩形组成,第 k 个矩形的右下角为 (rw,i) ( r w , i ) ,高度为 j j ,前 k 个矩形的最大权值和。
注: sum(l,r,i) s u m ( l , r , i ) 表示第 i i 列从第 l 个格子到第 r r 个格子的权值之和,可以用前缀和 O(1) 求出。
转移当然是从 (rw,i1) ( r w , i − 1 ) 转移到 (rw,i) ( r w , i )

f[i][j][k]=f[i1][j][k]+sum(rwj+1,rw,i) f [ i ] [ j ] [ k ] = f [ i − 1 ] [ j ] [ k ] + s u m ( r w − j + 1 , r w , i )

可以发现,上面的转移忽略了一种情况:第 k k 个矩形只占用一列。
所以,当 i 为奇数时:
f[i][j][k]=max(f[i][j][k],f[i1][<j][k1]+sum(rwj+1,rw,i)) f [ i ] [ j ] [ k ] = max ( f [ i ] [ j ] [ k ] , f [ i − 1 ] [ < j ] [ k − 1 ] + s u m ( r w − j + 1 , r w , i ) )

否则 i i 为偶数时:
f[i][j][k]=max(f[i][j][k],f[i1][>j][k1]+sum(rwj+1,rw,i))

上面两个转移在实现上可以用 前缀 / 后缀最大值 将转移优化到 O(1) O ( 1 )
边界比较复杂:
0im,1jrw,f[i][j][0]=0 ∀ 0 ≤ i ≤ m , 1 ≤ j ≤ r w , f [ i ] [ j ] [ 0 ] = 0

0im,f[i][0][0]=0 ∀ 0 ≤ i ≤ m , f [ i ] [ 0 ] [ 0 ] = 0

其余初始值为 − ∞
第一个边界比较好理解,关键是第二个边界中为什么第二维可以为 0 0
答案是:当第一个矩形的高度为 1 时,该状态不可能从 f[...][1][0] f [ . . . ] [ 1 ] [ 0 ] 转移。 (因为高度要 严格大于前一个矩形)
行号上界为 rw r w 时的最优解为:
i=1mj=1rwf[i][j][2K+1] ∑ i = 1 m ∑ j = 1 r w f [ i ] [ j ] [ 2 K + 1 ]

复杂度 O(n2mK) O ( n 2 m K )

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 102, M = 24, INF = 0x3f3f3f3f;
int n, m, K, a[N][N], sum[N][N], f[N][N][M], g[N][N][M], h[N][N][M], ans = -INF;
int solve(int rw) {
    int i, j, k; For (i, 1, m) sum[rw][i] = sum[rw - 1][i] + a[rw][i];
    For (i, 0, m) For (j, 0, rw + 1) For (k, 0, K)
        f[i][j][k] = g[i][j][k] = h[i][j][k] = -INF;
    For (j, 1, rw) f[0][j][0] = 0, g[0][j][0] = max(g[0][j - 1][0], f[0][j][0]);
    Rof (j, rw, 1) h[0][j][0] = max(h[0][j + 1][0], f[0][j][0]);
    f[0][0][0] = g[0][0][0] = 0; For (i, 1, m) For (k, 0, K) {
        if (!k) f[i][0][0] = g[i][0][0] = 0; For (j, 1, rw) {
            f[i][j][k] = !k ? 0 : sum[rw][i] - sum[rw - j][i] +
                max(f[i - 1][j][k], k & 1 ? g[i - 1][j - 1][k - 1]
                    : h[i - 1][j + 1][k - 1]);
        }
        For (j, 1, rw) g[i][j][k] = max(g[i][j - 1][k], f[i][j][k]);
        Rof (j, rw, 1) h[i][j][k] = max(h[i][j + 1][k], f[i][j][k]);
    }
    int ans = -INF; For (i, 1, m) ans = max(ans, g[i][rw][K]); return ans;
}
int main() {
    int i, j; n = read(); m = read(); K = read(); (K <<= 1)++;
    For (i, 1, n) For (j, 1, m) a[i][j] = read();
    For (i, 1, n) ans = max(ans, solve(i)); cout << ans << endl; return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值