BZOJ4380[POI2015] Myjnie

30 篇文章 0 订阅
23 篇文章 1 订阅

BZOJ4380[POI2015] Myjnie

Description

有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],c[i](1<=a[i]<=b[i]<=n1<=c[i]<=500000)

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

Solution:

神一样的dp…

首先先对c[i]离散。

因为与这道题相关的量是区间的最小值,所以定义 dp[i][j][k] 表示区间 [i,j] 的最小值为 k 的最大值。再将这个dp定义扩展一下:dp[i][j][k]=max(dp[i][j][t],t[k,Max])

所以再记一个 pre[i][j][k] 表示当前的 dp[i][j][k] 的”实际 k 值“,记一个fa[i][j][k]表示当前状态的最小值取在哪个点(用于输出方案)。

转移时:每一次用 O(nm) 算一个当最小值为 k 时,最小值在i点,可以消费的顾客数(顾客的区间被大区间完全包含)。转移方程:

dp[i][j][k]=max(cnt[pos][k]+dp[i][pos1][k]+dp[pos+1][j][k])(pos[i,j])

dp[i][j][k]=max(dp[i][j][k],dp[i][j][k+1])(k=Max0)

然后再根据 pre 数组和 fa 数组还原方案即可,复杂度 O(n3m)

#include<stdio.h>
#include<iostream>
#include<algorithm>
#define N 55
#define M 4005
using namespace std;
void Rd(int &res){
    char c;res=0;
    while(c=getchar(),!isdigit(c));
    do{
        res=(res<<1)+(res<<3)+(c^48);
    }while(c=getchar(),isdigit(c));
}
struct Node{int L,R,w;}A[N];
int dp[N][N][M],pre[N][N][M],fa[N][N][M],Ans[N],val[M],cnt[N][M];
void Pf(int L,int R,int k){
    k=pre[L][R][k];
    int pos=fa[L][R][k];
    Ans[pos]=val[k];
    if(pos>L)Pf(L,pos-1,k);
    if(pos<R)Pf(pos+1,R,k);
}
int main(){
    int n,m;
    Rd(n);Rd(m);
    for(int i=1;i<=m;i++){
        Rd(A[i].L);Rd(A[i].R);Rd(A[i].w);
        val[i-1]=A[i].w;
    }
    sort(val,val+m);
    int sz=unique(val,val+m)-val;
    for(int i=1;i<=m;i++)
        A[i].w=lower_bound(val,val+sz,A[i].w)-val;
    sz--;//[0,sz]
    for(int L=n;L>=1;L--)
        for(int R=L;R<=n;R++){
            for(int i=L;i<=R;i++)
                for(int j=0;j<=sz;j++)
                    cnt[i][j]=0;
            for(int i=1;i<=m;i++){
                if(A[i].L<L||A[i].R>R)continue;
                for(int j=A[i].L;j<=A[i].R;j++)
                    cnt[j][A[i].w]++;
            }
            for(int i=L;i<=R;i++)
                for(int j=sz-1;j>=0;j--)
                    cnt[i][j]+=cnt[i][j+1];
            pre[L][R][sz]=sz;
            for(int k=sz;k>=0;k--){
                int mx=-1,pos;
                for(int i=L;i<=R;i++){
                    int tmp1=0,tmp2=0;
                    if(i>L)tmp1=dp[L][i-1][k];
                    if(i<R)tmp2=dp[i+1][R][k];
                    int rs=val[k]*cnt[i][k]+tmp1+tmp2;
                    if(rs>mx)mx=rs,pos=i;
                }
                dp[L][R][k]=mx;
                fa[L][R][k]=pos;
                if(k<sz){
                    if(dp[L][R][k+1]>dp[L][R][k]){
                        dp[L][R][k]=dp[L][R][k+1];
                        pre[L][R][k]=pre[L][R][k+1];
                    }else pre[L][R][k]=k;
                }
            }
        }
    printf("%d\n",dp[1][n][0]);
    Pf(1,n,0);
    for(int i=1;i<=n;i++)
        printf("%d%c",Ans[i]," \n"[i==n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值