[POJ1821]Fence(单调队列优化dp)

[poj1821]Fence

有 N 块木板从左至右排成一行,有 M 个工匠对这些木板进行粉刷,每块木板至多被粉刷一次。第 i 个工匠要么不粉刷,要么粉刷包含木板 Si 的,长度不超过Li 的连续一段木板,每粉刷一块木板可以得到 Pi 的报酬。求如何安排能使工匠们获得的总报酬最多。
1<=N<=16000,1<=M<=100

输入

NK
L1 P1 S1
L2 P2 S2
...
LK PK SK

输出

输出包含一个整数,即最大总收入。

样例输入:

8 4
3 2 2
3 2 3
3 3 5
1 1 7

样例输出:

17
先把所有工匠按照(Si)排序,这样一来,每个工匠粉刷的木板一定在上一个工匠之后,使我们能够按顺序进行线性 DP。
(F[i,j])表示安排前(i)个工匠粉刷前(j)块木板(可以有空着不刷的木板),工匠能获得的最多报酬。
1.第(i)个工匠可以什么也不刷,此时(F[i,j]=F[i-1,j])
2.第(j)块木板可以空着不刷,此时(F[i,j]=F[i,j-1])
3.第(i)个工匠粉刷第(k+1)块到第(j)块木板。根据题意,该工匠粉刷总数不能超过(Si),所以需要满足: (k+1<=Si<=j)并且(j-k<=Li)。于是,有状态转移方程:

[F[i,j]=max(F[i-1,k]+Pi*(j-k)) (j-Li<=k<=Si-1,j>=Si) ]

我们重点来看这个方程怎么优化。首先,在考虑内层循环(j)以及决策(k)时,可把外层循环变量(i)看作定值。这样一来,状态转移方程中的各项可分为两部分:
1.(Pi*j),除定值(i)外,只有状态变量(j)
2.(F[i-1,k]-Pi*k),除定值(i)外,只有决策变量(k)。状态转移方程可写为:

[F[i,j]=Pi*j+max(F[i-1,k]-Pi*k) ]

(j) 增大时,(k) 的取值范围上界 (Si-1) 不变,下界 (j-Li) 变大。这时
我们来比较任意两个决策 (k1)(k2)。不妨设 (k1<k2<=Si-1)。因为 (k2)(k1) 更靠后,所以随着 (j) 的增加,(k1) 会比 (k2) 更早从范围([j-Li,Si-1])中排除。如果还满足(F[i-1,k1]-Pi*k1<=F[i-1,k2]-Pi*k2)那么就意味着 k2 不但比 k1 优,还比 k1 的存活时间更长。在这种情况下,k1 就是一个无用的决策,应该被排除出候选集合。综上所述,我们可以维护一个决策点 k 的单调递增。数值(F[i-1,k]-Pi*k) 单调递减的队列。只有这个队列中的决策才有可能在某一时刻成为最优决策。这个单调队列支持如下操作:
1.当 j 变大时,检查队头元素,把小于 j-Li 的决策出队。
2.需要查询最优决策时,队头即为所求。
3.有一个新的决策需要加入队列时,在队尾检查 F[i-1,k]-Pi*k 的单调性,把无用决策从队尾直接出队,最后把新决策加入队列。
在本题具体来说,当内循环开始时(j==Si),建立一个空的单调队列,把(max(Si-Li,0),Si-1)中的决策依次加入候选集合(执行操作 3)。对于每个 (j=Si~k),先在队头检查决策合法性(操作 1),然后取队头为最优决策(操作 2)进行状态转移。因为每个决策至多入队,出队一次,故转移的时间复杂队均弹 (O(1))。整个算法的时间复杂度为 (O(NM))

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
int read()
{
	int x=0,w=1;char ch=getchar();
	while(ch>'9'||ch<'0') {if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*w;
}
int dp[110][16010];
int team[16010];
struct node{
int l,p,s;
}f[110];
int cmp(node p,node q)
{
	return p.s<q.s;
}
int main()
{
	int n=read(),k=read();
	for(int i=1;i<=k;i++)
	{
		f[i].l=read();f[i].p=read();f[i].s=read();
	}
	sort(f+1,f+1+k,cmp);
	for(int i=1;i<=k;i++)
	{
		int l=1,r=0;
		for(int p=max(0,f[i].s-f[i].l);p<f[i].s;p++)
		{
			while(l<=r&&dp[i-1][team[r]]-f[i].p*team[r]<=dp[i-1][p]-f[i].p*p)
			r--;
			team[++r]=p;
		}
		for(int j=1;j<=n;j++)
		{
			dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			if(j>=f[i].s)
			{
				while(l<=r&&team[l]<j-f[i].l) l++;
				if(l<=r)
				dp[i][j]=max(dp[i][j],dp[i-1][team[l]]+f[i].p*(j-team[l]));
			}
		}
	}
	cout<<dp[k][n];
}
原文地址:https://www.cnblogs.com/lsgjcya/p/9071942.html