poj 1821单调队列优化dp

poj 1821 Fence

题目大意有

一段长 n n n的篱笆 ( 1 ≤ N ≤ 16000 ) (1 \leq N \leq 16 000) (1N16000)需要 m m m个人来刷 ( 1 ≤ K ≤ 100 ) (1 \leq K \leq 100) (1K100)每个工人有 3 3 3个属性 l , p , s l,p,s l,p,s分别表示工人最多刷连续 l l l段篱笆,每刷一段可以得到 p p p的回报,工人最初的位置在 s s s工人必须刷连续的篱笆,并且要么不刷,刷就必须把自己面前的一段篱笆刷掉(面前的这一段也计入总费用)现在请你安排这 m m m个工人,使他们的收益和最大

读入格式

第一行两个正整数 n , k n,k n,k接下来的 k k k行,每行 3 3 3个正整数 l , p , s l,p,s l,p,s

输出格式

输出一行正整数表示工人们的最大收益。

思路

设计 d p dp dp的状态 d p [ i ] [ j ] 表 示 前 i 个 人 刷 到 了 第 j 个 栅 栏 的 最 大 代 价 d p[i][j]表示前i个人刷到了第j个栅栏的最大代价 dp[i][j]ij
转移:
d p [ i ] [ j ] dp[i][j] dp[i][j]可以从 d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ k − 1 ] + ( j − k + 1 ) ∗ p i dp[i-1][j],dp[i][j-1],dp[i-1][k-1]+(j-k+1)*p_i dp[i1][j],dp[i][j1],dp[i1][k1]+(jk+1)pi转移
d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]表示第j栅栏不刷
d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]表示第 i i i个人不刷 j j j
d p [ i − 1 ] [ k ] + ( j − k + 1 ) dp[i-1][k]+(j-k+1) dp[i1][k]+(jk+1) i i i个人从 k k k开始一直刷到 j j j(其中 k k k需要枚举)
这个样子就得到正确答案了
但是考虑到第 3 3 3种转移最差可能使 O ( N ) O(N) O(N)
这样总体复杂度可能会达到 O ( m ∗ n 2 ) O(m*n^2) O(mn2)
所以我们需要将 d p dp dp优化

先放上未经优化的 d p dp dp代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int read()
{
	int r,s=0,c;
	for(;!isdigit(c=getchar());s=c);
	for(r=c^48;isdigit(c=getchar());(r*=10)+=c^48);
	return s^45?r:-r;
}
const int N=110,L=16100;
int n,m;
struct node
{
	int l,p,s;
}a[N];
bool cmp(node a,node b)
{
	return a.s<b.s;
}
int dp[N][L];
int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++)
		a[i].l=read(),a[i].p=read(),a[i].s=read();
	sort(a+1,a+1+m,cmp);
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
		{
			dp[i][j]=max(dp[i][j],dp[i][j-1]);//这块不刷
 			dp[i][j]=max(dp[i][j],dp[i-1][j]);//这个人不刷
 			if(j<a[i].s)continue;
 			for(int k=max(1,a[i].s-a[i].l);k<=a[i].s;k++)
 			{
 				if((j-k+1)>a[i].l)continue;
 				dp[i][j]=max(dp[i][j],dp[i-1][k-1]+(j-k+1)*a[i].p);
 			}
 		}
 	}
 	printf("%d\n",dp[m][n]);
 	return 0;
}
优化

我们再次看一下转移:
d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]表示第j栅栏不刷
d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]表示第 i i i个人不刷 j j j
d p [ i − 1 ] [ k ] + ( j − k + 1 ) dp[i-1][k]+(j-k+1) dp[i1][k]+(jk+1) i i i个人从 k k k开始一直刷到 j j j(其中 k k k需要枚举)
这样只有第 3 3 3种转移方式可以优化
我们把第 3 3 3种变一下型可以得到 d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ k − 1 ] − k ∗ p i } + ( j + 1 ) ∗ p i dp[i][j]=max \{ {dp[i-1][k-1]-k*p_i} \}+(j+1)*p_i dp[i][j]=max{dp[i1][k1]kpi}+(j+1)pi
这个样子就可以进行单调队列的优化了

将单调队列优化分成 3 3 3
1 、 1、 1 1 1 1 s i − 1 s_i-1 si1只进入单调队列不转移
2 、 2、 2 s i s_i si既进入单调队列也转移
3 、 3、 3 s i + 1 s_i+1 si+1 n n n只转移不进入单调队列

注意:要将不符合条件的状态及时清出单调队列,只有当队列不为空的时候才能转移

下面附上单调队列优化过后的 A C AC AC代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int read()
{
 	int r,s=0,c;	
 	for(;!isdigit(c=getchar());s=c);
 	for(r=c^48;isdigit(c=getchar());(r*=10)+=c^48);
 	return s^45?r:-r;
}
const int N=110,L=16100;
int n,m;
struct node
{	
	int l,p,s;
}a[N];
bool cmp(node a,node b)
{	
	return a.s<b.s;
}
int dp[N][L];nt q[L],f,b;
int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++)
 		a[i].l=read(),a[i].p=read(),a[i].s=read();
	sort(a+1,a+1+m,cmp);
	for(int i=1;i<=m;i++)
 	{
 		f=1,b=0;
 		for(int j=1;j<a[i].s;j++)
 		{
  			dp[i][j]=max(dp[i][j],dp[i][j-1]);//这块不刷 
  			dp[i][j]=max(dp[i][j],dp[i-1][j]);//这个人不刷 
   			while((f<=b)&&(j-q[f]+1>a[i].l))f++;
   			while((f<=b)&&(dp[i-1][q[b]-1]-q[b]*a[i].p<=dp[i-1][j-1]-j*a[i].p))b--;
   			q[++b]=j;
  		}
 		dp[i][a[i].s]=max(dp[i][a[i].s],dp[i][a[i].s-1]);//这块不刷 
 		dp[i][a[i].s]=max(dp[i][a[i].s],dp[i-1][a[i].s]);//这个人不刷 
 		while((f<=b)&&(a[i].s-q[f]+1>a[i].l))f++;	
		while((f<=b)&&(dp[i-1][q[b]-1]-q[b]*a[i].p<=dp[i-1][a[i].s-1]-a[i].s*a[i].p))b--;
 		q[++b]=a[i].s;
 		dp[i][a[i].s]=max(dp[i][a[i].s],dp[i-1][q[f]-1]-q[f]*a[i].p+(a[i].s+1)*a[i].p);
 		for(int j=a[i].s+1;j<=n;j++)
 		{
  			dp[i][j]=max(dp[i][j],dp[i][j-1]);//这块不刷 
  			dp[i][j]=max(dp[i][j],dp[i-1][j]);//这个人不刷 
  			while((f<=b)&&(j-q[f]+1>a[i].l))f++;
  			if(f<=b)
  				dp[i][j]=max(dp[i][j],dp[i-1][q[f]-1]-q[f]*a[i].p+(j+1)*a[i].p);
 		}
	}
	printf("%d\n",dp[m][n]);
 	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值