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
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值