poj 1821单调队列优化dp

本文针对POJ1821 Fence问题,详细解析了一种动态规划(DP)算法及其优化策略,旨在最大化工人刷篱笆的总收益。通过引入单调队列优化,有效减少了状态转移的时间复杂度,提高了算法效率。

poj 1821 Fence

题目大意有

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

读入格式

第一行两个正整数n,kn,kn,k接下来的kkk行,每行333个正整数l,p,sl,p,sl,p,s

输出格式

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

思路

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

先放上未经优化的dpdpdp代码

#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;
}
优化

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

将单调队列优化分成333
1、1、1111si−1s_i-1si1只进入单调队列不转移
2、2、2sis_isi既进入单调队列也转移
3、3、3si+1s_i+1si+1nnn只转移不进入单调队列

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

下面附上单调队列优化过后的ACACAC代码

#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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值