BZOJ 4380: [POI2015]Myjnie 区间dp

4380: [POI2015]Myjnie

Time Limit: 40 Sec  Memory Limit: 256 MBSec  Special Judge
Submit: 327  Solved: 167
[Submit][Status][Discuss]

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]<=n,1<=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


这应该是dp白痴BJ做过最强的区间dp了...

我们发现 决策与其区间最小值有关 所以把c搞进去

所以我们用f[i][j][k] 表示在区间[i,j]内所有权值>=k的最大收益

之后怎么转移呐

考虑[l,r]每次从[l+1,r] [l,r-1]转移 //手动滑稽

枚举一个点k

f[l][r][j]=max(f[l][k-1]+f[k+1][r]+c[j]*num[k][j])

其中num的含义是

l<=a<=k<=b<=r 且可以接受价格>=c[j]的个数


至于输出方案 就中间记下决策点 然后dfs一边就好了


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<set>
#include<map>
using namespace std;

typedef long long ll;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=55,M=4010,inf=0X3f3f3f3f;

struct node
{
	int a,b,c;
	friend bool operator <(const node &x,const node &y)
	{return x.c<y.c;}
}p[N];

int f[N][N][M],g[N][N][M],last[N][N][M];
int num[N][M];

int val[N];

void dfs(int l,int r,int lim)
{
	if(l>r) return ;
	int tmp[2];
	tmp[0]=g[l][r][lim],tmp[1]=last[l][r][lim];
	val[tmp[0]]=p[tmp[1]].c;
	dfs(l,tmp[0]-1,tmp[1]);dfs(tmp[0]+1,r,tmp[1]);
}

int main()
{
	int n=read(),m=read();
	register int l,r,i,j,len;
	for(i=1;i<=m;++i)
		p[i].a=read(),p[i].b=read(),p[i].c=read();
	sort(p+1,p+1+m);
	for(l=1;l<=n;++l)
		for(r=l;r<=n;++r)
			for(j=1;j<=m;++j)
				f[l][r][j]=-inf;
	for(len=0;len<n;++len)
		for(l=1;l+len<=n;++l)
		{
			r=l+len;
			for(i=l;i<=r;++i)
				for(j=1;j<=m;++j)
					num[i][j]=0;
			for(j=1;j<=m;++j)
				if(p[j].a>=l && p[j].b<=r)
					for(i=p[j].a;i<=p[j].b;++i)
						num[i][j]++;
			for(i=l;i<=r;++i)
				for(j=m;j;j--)
					num[i][j]+=num[i][j+1];
			for(i=l;i<=r;++i)
				for(j=1;j<=m;++j)
					if(f[l][r][j]<f[l][i-1][j]+f[i+1][r][j]+p[j].c*num[i][j])
						f[l][r][j]=f[l][i-1][j]+f[i+1][r][j]+p[j].c*num[i][j],
						g[l][r][j]=i,last[l][r][j]=j;
			for(j=m;j;j--)
				if(f[l][r][j]<f[l][r][j+1])
					f[l][r][j]=f[l][r][j+1],
					g[l][r][j]=g[l][r][j+1],last[l][r][j]=last[l][r][j+1];
		}
	dfs(1,n,1);
	print(f[1][n][1]);putchar('\n');
	for(i=1;i<=n;++i)
		print(val[i]),putchar(' ');
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值