zoukankan      html  css  js  c++  java
  • [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];
    }
    
  • 相关阅读:
    C中的system函数
    结构体数组
    转载--C++的反思
    oracle临时表空间
    oracle行转列,列转行
    oracle查询表结构语句
    实例化内部类
    Java非静态内部类为什么不能有静态成员
    oracle显示转换字段类型cast()函数
    linux中vim常用命令
  • 原文地址:https://www.cnblogs.com/lsgjcya/p/9071942.html
Copyright © 2011-2022 走看看