@bzoj - [email protected] [POI2015] Myjnie

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

有 n 家洗车店从左往右排成一排,每家店都有一个正整数价格 p[i]。

有 m 个人要来消费,第 i 个人会驶过第 a[i] 个开始一直到第 b[i] 个洗车店,且会选择这些店中最便宜的一个进行一次消费。但是如果这个最便宜的价格大于 c[i],那么这个人就不洗车了。

请给每家店指定一个价格,使得所有人花的钱的总和最大。

input

第一行包含两个正整数 n, m(1<=n<=50,1<=m<=4000)。

接下来 m 行,每行包含三个正整数 a[i], b[i], ci

output

第一行输出一个正整数,即消费总额的最大值。

第二行输出 n 个正整数,依次表示每家洗车店的价格 p[i],要求 1<=p[i]<=500000。

若有多组最优解,输出任意一组。

sample input

7 5

1 4 7

3 7 13

5 6 20

6 7 1

1 2 5

sample output

43

5 5 13 13 20 20 13

@[email protected]

首先,洗车店的价格一定是某顾客的 c 值,否则可以通过调整变得更优。

像这样的区间最值相关问题可以考虑其笛卡尔树的结构。比如这道题就可以用笛卡尔树来 dp。

具体来讲,我们定义状态 dp(i, j, k),表示区间 [i, j] 的最小值为 k 能够取得的最大利益。根据最先的结论,我们 k 的取值只有 O(m) 个。

通过枚举 [i, j] 的最小值位置 p,我们可以进行转移:

\[dp(i, j, k) = dp(i, p-1, x) + dp(p+1, j, y) + f(i, j, p, k)\]

其中要求 x, y >= k,权值函数 f(i, j, p, k) 表示这一个状态能够赚得的顾客利益。

f(i, j, p, k) 会计算到哪些顾客呢?假如第 q 个顾客被计算到了,那么必然有 i <= a[q] <= p <= b[q] <= j 且 c[q] >= k。

通过从大到小枚举 k,可以快速地维护 f。

我们通过维护后缀最大值,就可以不必再枚举 x 和 y。

因为要输出方案,记得记录当前状态是怎么转移过来的。最后递归输出即可。

总时间复杂度 O(n^3*m)。

@accepted [email protected]

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 50;
const int MAXM = 4000;
struct node{
    int le, ri;
    int lim;
}a[MAXM + 5];
bool operator < (node a, node b) {
    return a.lim < b.lim;
}
int n, m;
int ans[MAXN + 5];
int mx[MAXN + 5][MAXN + 5][MAXM + 5];
int dp[MAXN + 5][MAXN + 5][MAXM + 5];
int val[MAXN + 5][MAXN + 5][MAXN + 5];
int pre[MAXN + 5][MAXN + 5][MAXM + 5];
int mxpos[MAXN + 5][MAXN + 5][MAXM + 5];
void GetAnswer(int le, int ri, int p) {
    if( mx[le][ri][p] == 0 ) {
        for(int i=le;i<=ri;i++)
            ans[i] = a[p].lim;
        return ;
    }
    int q = mxpos[le][ri][p];
    int r = pre[le][ri][q];
    ans[r] = a[q].lim;
    GetAnswer(le, r-1, q);
    GetAnswer(r+1, ri, q);
}
int main() {
    scanf("%d%d", &n, &m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d", &a[i].le, &a[i].ri, &a[i].lim);
    sort(a+1, a+m+1);
    for(int len=1;len<=n;len++)
        for(int i=1;i+len-1<=n;i++) {
            int j = i + len - 1;
            for(int k=m;k>=1;k--) {
                if( i <= a[k].le && a[k].ri <= j ) {
                    for(int l=i;l<=j;l++) {
                        if( a[k].le <= l && l <= a[k].ri )
                            val[i][j][l]++;
                        if( mx[i][l-1][k] + mx[l+1][j][k] + val[i][j][l]*a[k].lim > dp[i][j][k] ) {
                            pre[i][j][k] = l;
                            dp[i][j][k] = mx[i][l-1][k] + mx[l+1][j][k] + val[i][j][l]*a[k].lim;
                        }
                    }
                }
                if( k == m || dp[i][j][k] >= mx[i][j][k+1] ) {
                    mx[i][j][k] = dp[i][j][k];
                    mxpos[i][j][k] = k;
                }
                else {
                    mx[i][j][k] = mx[i][j][k+1];
                    mxpos[i][j][k] = mxpos[i][j][k+1];
                }
            }
    }
    printf("%d\n", mx[1][n][1]);
    GetAnswer(1, n, 1);
    for(int i=1;i<=n;i++)
        printf("%d ", ans[i]);
    puts("");
}

@[email protected]

其实不需要离散化,因为即使 c 值重复也影响不大。

一开始因为区间 dp 的某些错误 dp 方式 WA 了几次(捂脸)。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10359188.html

时间: 2024-10-14 04:53:36

@bzoj - [email protected] [POI2015] Myjnie的相关文章

@bzoj - [email&#160;protected] [POI2015] Kinoman

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 共有 m 部电影,第 i 部电影的好看值为 w[i]. 在 n 天之中每天会放映一部电影,第 i 天放映的是第 f[i] 部. 你可以选择 l, r (1 <= l <= r <= n) ,并观看第 l, l+1, -, r 天内所有的电影. 最大化观看且仅观看过一次的电影的好

@bzoj - [email&#160;protected] [POI2015] Wilcze do?y

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个长度为 n 的序列,你有一次机会选中一段连续的长度不超过 d 的区间,将里面所有数字全部修改为 0. 请找到最长的一段连续区间,使得该区间内所有数字之和不超过 p . input 第一行包含三个整数 n, p, d (1 <= d <= n <= 2000000,0 &

@bzoj - [email&#160;protected] [POI2015] Pustynia

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个长度为 n 的正整数序列 a,每个数都在 1 到 10^9 范围内. 告诉你其中 s 个数,并给出 m 条信息,每条信息包含三个数 l, r, k 以及 k 个正整数,表示 a[l], a[l+1], ..., a[r-1], a[r] 里这 k 个数中的任意一个都比任意一个剩

@bzoj - [email&#160;protected] [POI2015] Logistyka

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 维护一个长度为 n 的序列,一开始都是 0,支持以下两种操作: 1.U k a 将序列中第 k 个数修改为 a. 2.Z c s 在这个序列上,每次选出 c 个正数,并将它们都减去 1,询问能否进行 s 次操作. 每次询问独立,即每次询问不会对序列进行修改. input 第一行包含两个

@bzoj - [email&#160;protected] [POI2015] Odwiedziny

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵 n 个点的树,树上每条边的长度都为 1 ,第 i 个点的权值为 a[i]. Byteasar 会按照某个 1 到 n 的全排列 b 走 n-1 次,第 i 次他会从 b[i] 点走到 b[i+1] 点,并且这一次的步伐大小为 c[i]. 对于一次行走,假设起点为 x,终点为

@bzoj - [email&#160;protected] [POI2015] ?asuchy

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 圆桌上摆放着 n 份食物,围成一圈,第 i 份食物所含热量为 c[i]. 相邻两份食物之间坐着一个人,共有 n 个人.每个人有两种选择,吃自己左边或者右边的食物.如果两个人选择了同一份食物,这两个人会平分这份食物,每人获得一半的热量. 假如某个人改变自己的选择后(其他 n-1 个人的选

@bzoj - [email&#160;protected] [POI2015] Wycieczki

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一张 n 个点 m 条边的带权有向图,每条边的边权只可能是1,2,3中的一种. 将所有可能的路径按路径长度排序,请输出第 k 小的路径的长度,注意路径不一定是简单路径,即可以重复走同一个点. input 第一行包含三个整数 n, m, k (1<=n<=40,1<=m<

@bzoj - [email&#160;protected] [Poi2011]Lightning Conductor

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @version - [email protected] @version - [email protected] @[email protected] @[email protected] 已知一个长度为

@bzoj - [email&#160;protected] [POI2014]Hotel加强版

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵树,求无序三元组 (a, b, c) 的个数,使得 dis(a, b) = dis(b, c) = dis(c, a),且 a ≠ b, b ≠ c, c ≠ a. inpu