[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];
}

猜你喜欢

转载自www.cnblogs.com/lsgjcya/p/9071942.html