bzoj 4380 [POI2015]Myjnie 区间dp

题面

题目传送门

解法

有一点技巧的区间dp

  • 显然,对于每一个洗车店最后的价格,一定是某一个人想要的价格 c c 。如果不是且这个人的要求被满足了,那么一定可以将这个洗车店的价格正好改到c,最后的总收益变大
  • 那么,我们考虑这样一个dp:设 f[i][j][k] f [ i ] [ j ] [ k ] 表示区间 [i,j] [ i , j ] 的洗车店中,价格最小的为 k k 。其中,我们先对每一个人的价格离散化一下,k是某一个价格对应的编号
  • 考虑如何转移。我们不妨枚举中间哪一个是最小值,假设位置为 l l ,那么就可以得到转移方程:f[i][j][k]=max(f[i][l1][k]+f[l+1][j][k]+cnt[k][l]×c[k])。其中, k k ′ k′′ k ″ 分别为区间 [i,l1] [ i , l − 1 ] [l+1,j] [ l + 1 , j ] 的最小值,且满足 kk,k′′ k ≤ k ′ , k ″ cnt[k][l] c n t [ k ] [ l ] 为在 l l 这个位置有多少个人的价格不超过k c[k] c [ k ] 为离散化后编号为 k k 的实际价格是多少
  • 然而,我们发现这个转移的复杂度其实是O(nm2)的,无法接受
  • 观察这个式子,可以发现,因为 k,k′′ k ′ , k ″ 一定不小于 k k ,对应的是一段后缀,那么我们可以令g[i][j][k]=max(f[i][j][k]) (kk),那么转移的复杂度就变成了 O(n) O ( n )
  • 因为要求出具体的方案,所以我们可以在转移的时候记录一下这个状态从哪里转移而来的即可
  • 时间复杂度: O(n3m) O ( n 3 m )
    【注意事项】
  • 统计 cnt c n t 数组的时候需要注意,有点细节

代码

#include <bits/stdc++.h>
#define M 4010
#define N 55
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
    int l, r, v, id;
} a[M];
int ans[N], b[M], sum[M][N], f[N][N][M], g[N][N][M], h[N][N][M], pre[N][N][M];
void dfs(int l, int r, int k) {
    if (l > r) return;
    int x = pre[l][r][k]; ans[x] = b[k];
    dfs(l, x - 1, h[l][x - 1][k]);
    dfs(x + 1, r, h[x + 1][r][k]);
}
main() {
    int n, m, len = 0; read(n), read(m);
    for (int i = 1; i <= m; i++)
        read(a[i].l), read(a[i].r), read(a[i].v), b[++len] = a[i].v;
    sort(b + 1, b + len + 1); len = unique(b + 1, b + len + 1) - b - 1;
    map <int, int> hh;
    for (int i = 1; i <= len; i++) hh[b[i]] = i;
    for (int i = 1; i <= m; i++) a[i].id = hh[a[i].v];
    for (int ll = 1; ll <= n; ll++)
        for (int i = 1; i <= n - ll + 1; i++) {
            int j = i + ll - 1;
            for (int k = 1; k <= len; k++)
                for (int l = 1; l <= n; l++)
                    sum[k][l] = 0;
            for (int k = 1; k <= m; k++)
                if (i <= a[k].l && a[k].r <= j) sum[a[k].id][a[k].l]++, sum[a[k].id][a[k].r + 1]--;
            for (int k = 1; k <= len; k++)
                for (int l = 1; l <= n; l++)
                    sum[k][l] += sum[k][l - 1];
            for (int k = len - 1; k; k--)
                for (int l = 1; l <= n; l++)
                    sum[k][l] += sum[k + 1][l];
            for (int k = 1; k <= len; k++)
                for (int l = i; l <= j; l++) {
                    int tmp = g[i][l - 1][k] + g[l + 1][j][k] + sum[k][l] * b[k];
                    if (f[i][j][k] <= tmp) f[i][j][k] = tmp, pre[i][j][k] = l;
                }
            g[i][j][len] = f[i][j][len]; h[i][j][len] = len;
            for (int k = len - 1; k; k--) {
                g[i][j][k] = max(g[i][j][k + 1], f[i][j][k]);
                if (g[i][j][k] == g[i][j][k + 1]) h[i][j][k] = h[i][j][k + 1];
                    else h[i][j][k] = k;
            }
        }
    cout << g[1][n][1] << "\n";
    int mxi;
    for (int i = 1; i <= len; i++)
        if (f[1][n][i] == g[1][n][1]) {mxi = i; break;}
    dfs(1, n, mxi);
    for (int i = 1; i <= n; i++) cout << ans[i] << ' '; cout << "\n";
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值