bzoj5359: [Lydsy1805月赛]寻宝游戏 复杂计数Dp

bzoj5359: [Lydsy1805月赛]寻宝游戏

分析

计数Dp什么的果然最烦了。
这道题主要是状态设计很难。
首先我们要把问题转化。
替换可以等价于把经过路径上的x个数不计入答案,不经过路径上的x个数计入答案所获得的最大收益。
然后把这个问题一般化
把经过路径上的k个数不计入答案,不经过路径上的l个数计入答案所获得的最大受益
这样的话原问题是当前问题的一个特殊情况,即 k=l k = l 的情况。
这样的话状态就设计出来了 f[i][j][k][l] f [ i ] [ j ] [ k ] [ l ] 表示处理完 (1,1)>(i,j) ( 1 , 1 ) − > ( i , j ) 路径内不计入k个数,路径外计入l个数的最大收益。
考虑转移。
往右走一步,意味着 (i,j+1) ( i , j + 1 ) 被选入路径之中。
这个时候就有两种决策:把 a[i][j+1] a [ i ] [ j + 1 ] 计入答案,或者不计入。
同时,往右走一步也意味着 ([i+1,n],j) ( [ i + 1 , n ] , j ) 上的数都不可能作为路径内的数计入答案,于是它们有可能成为路径外的数计入答案。
这个时候可以枚举 t[0,ni] t ∈ [ 0 , n − i ] ,表示这个路径上选 t t 个数放到路径外的数中。
这个时候可以简单贪心得到,我们取前t大的数是最优的,为了方便,定义 g[i][j][t] g [ i ] [ j ] [ t ] 表示 ([i+1,n],j) ( [ i + 1 , n ] , j ) 中的前 t t 大数之和。
于是得到转移方程
f[i][j][k][l]+g[i][j][t]+a[i+1][j]>f[i+1][j][k][l+t]
f[i][j][k][l]+g[i][j][t]>f[i+1][j][k+1][l+t] f [ i ] [ j ] [ k ] [ l ] + g [ i ] [ j ] [ t ] − > f [ i + 1 ] [ j ] [ k + 1 ] [ l + t ]
类似定义 h h 数组,转移向下走的方程,最后的答案就是
Maxmization(f[n][m][i][i]),i[0,K]
具体看代码。
计数Dp的状态设计真的很重要。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define mem(x, y) memset(x, y, sizeof(x))
#define rep(i, j, k) for(int i = j; i <= k; ++i)
const int N = 55, M = 22;
int ri() {
    char ch = getchar(); int x = 0;
    for(;ch < '0' || ch > '9'; ch = getchar()) ;
    for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) - '0' + ch;
    return x;
}
int f[N][N][M][M], g[N][N][N], h[N][N][N], a[N][N], x[N], n, m, K;
bool cmp(int x, int y) {return x > y;}
void Up(int &a, int b) {a = std::max(a, b);}
void Pre() {
    n = ri(); m = ri(); K = ri();
    rep(i, 1, n) rep(j, 1, m) a[i][j] = ri();
    mem(g, 0); mem(h, 0); mem(f, 0x80);
    for(int i = 1, tp = 0;i <= n; ++i, tp = 0)
        for(int j = m; j; --j) {
            std::sort(x + 1, x + tp + 1, cmp);
            for(int k = 1; k <= tp; ++k) g[i][j][k] = g[i][j][k - 1] + x[k];
            x[++tp] = a[i][j];
        }
    for(int j = 1, tp = 0;j <= m; ++j, tp = 0)
        for(int i = n; i; --i) {
            std::sort(x + 1, x + tp + 1, cmp);
            for(int k = 1; k <= tp; ++k) h[i][j][k] = h[i][j][k - 1] + x[k];
            x[++tp] = a[i][j];
        }
    f[1][1][0][0] = a[1][1]; f[1][1][1][0] = 0;
}
void Dp() {
    rep(i, 1, n) rep(j, 1, m) rep(k, 0, K) rep(l, 0, K) {
        rep(t, 0, std::min(K - l, m - j)) //Down
            Up(f[i + 1][j][k][l + t], f[i][j][k][l] + g[i][j][t] + a[i + 1][j]),
            Up(f[i + 1][j][k + 1][l + t], f[i][j][k][l] + g[i][j][t]); 
        rep(t, 0, std::min(K - l, n - i)) //Right
            Up(f[i][j + 1][k][l + t], f[i][j][k][l] + h[i][j][t] + a[i][j + 1]),
            Up(f[i][j + 1][k + 1][l + t], f[i][j][k][l] + h[i][j][t]); 
    }
    int ans = 0;
    for(int i = 0; i <= K; ++i) Up(ans, f[n][m][i][i]);
    printf("%d\n", ans);
}
int main() {
    for(int T = ri(); T--;) Pre(), Dp();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值