1. 程式人生 > >[poj 1821] Fence {單調佇列優化dp}

[poj 1821] Fence {單調佇列優化dp}

題目

http://poj.org/problem?id=1821


解題思路

先把所有工匠按照 S i S_{i} 排序,這樣,每個工匠粉刷的木板一定在上一個工匠粉刷的木板之後。
F [

i , j ] F[i,j] 表示安排前 i i 個工匠粉刷前 j
j
塊木板,工匠們能獲得的最多報酬:

  1. F [ i , j ] =
    F [ i 1 , j ] F[i,j]=F[i-1,j]
  2. F [ i , j ] = F [ i , j 1 ] F[i,j]=F[i,j-1]
  3. F [ i , j ] = m a x { F [ i 1 , k ] + P i ( j k ) } F[i,j]=max\{ F[i-1,k]+P_{i}*(j-k) \} ,其中 j L i k S i 1 , j S i j-L_{i}\leq k \leq S_{i}-1,j \geq S_{i}

然後我們可以用單調佇列維護一個決策點 k k 單調遞增,數值 F [ i 1 , k ] P i k F[i-1,k]-P_{i}*k 單調遞減的佇列。支援如下幾種操作:

  1. j j 變大時,檢查隊頭元素,把小於 j L i j-L_{i} 的決策出隊。
  2. 需要查詢最有決策時,隊頭即為所求。
  3. 有一個新的決策要加入候選集合時,在隊尾檢查 F [ i 1 , k ] P i k F[i-1,k]-P_{i}*k 的單調性,把無用決策從隊尾直接出隊,最後把新決策加入佇列。

程式碼

#include<cstdio>
#include<algorithm>
using namespace std; 
struct rec{int L,P,S;}a[110];
int n,m; 
int f[110][16010],q[16010]; 
bool operator <(rec a,rec b){return a.S<b.S;}
int calc(int i,int k){return f[i-1][k]-a[i].P*k;}
int main()
{
	scanf("%d%d",&n,&m); 
	for (int i=1;i<=m;i++) scanf("%d%d%d",&a[i].L,&a[i].P,&a[i].S);
	sort(a+1,a+m+1); 
	for (int i=1;i<=m;i++){
		int l=1,r=0; //初始化單調佇列
		for (int k=max(0,a[i].S-a[i].L);k<=a[i].S-1;k++){
			while (l<=r&&calc(i,q[r])<=calc(i,k)) r--; //插入新決策,維護隊尾單調性
			q[++r]=k; 
		}
		for (int j=1;j<=n;j++){
			f[i][j]=max(f[i-1][j],f[i][j-1]);		//不粉刷時的轉移
			if (j>=a[i].S) {
				while (l<=r&&q[l]<j-a[i].L) l++; //排除隊頭不合法決策
				if (l<=r) f[i][j]=max(f[i][j],calc(i,q[l])+a[i].P*j); //佇列非空時,取隊頭進行狀態轉移
			}
		}
	}
	printf("%d",f[m][n]); 
}