POJ 1821 Fence ★(单调队列优化DP)

题目大意:有一道线性篱笆由N个连续的木板组成。有K个工人,你要叫他们给木板涂色。每个工人有3个参数:L 表示 这个工人可以涂的最大木板数目,S表示这个工人站在哪一块木板,P表示这个工人每涂一个木板可以得到的钱。要注意的是,工人i可以选择不涂任何木板,否则,他的涂色区域必须是连续的一段,并且S[i]必须包含在内。 最后还有,每块木板只能被涂一次。   状态方程比较容易想:dp[i][j]表示第i个人刷的最后一面墙是j时的最大获利,则 dp[i][j]=max(dp[i-1][k]+(j-k)*p[i]) j-l[i]+1<=k+1<=s[i]         (*) dp[i][j]=max(dp[i-1][j],dp[i][j-1],dp[i][j])//第i个人不刷,第j面墙不刷   但是O(k*n^2)的复杂度显然不能接受。而针对(*)这种“特殊”的转移方程,我们可以用单调队列把它优化到O(1): dp[i][j]=max(dp[i-1][k]-k*p[i])+j*p[i] 其中j*p[i]在i,j两重循环中相当于常数,所以,对于状态dp[i][j]只要单调队列维护dp[i-1][k]-k*p[i]的最大值即可   单调队列维护过程(可以回过头看看POJ 2823---单调队列的模型): 单调队列具体的做法是:最外层循环为i,首先把j=1~s[i]-1转移完(因为它不涉及第三个转移),然后把(j-l[i]<=k<=s[i]-1)的决策点的F[i-1,k]-p[i]*k依次入队建立“入队早晚时间递增,F[i-1,k]-p[i]*k的值递减”的单调队列,接下来循环j=s[i]~s[i]+l[i]-1,进行这三个转移(第三个转移只需要用队首元素),其中每次需要把队首超出长度限制的决策点出队;最后把剩下的到n循环完,只需要前两个转移。  

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MID(x,y) ((x+y)>>1)
using namespace std;
typedef long long LL;
struct worker{
    int l, p, s;
}w[105];
bool cmp(worker a, worker b){
    return a.s < b.s;
}
int f[105][16005];
int n,k;
void debug(){
    for (int i = 0; i <= k; i ++){
        for (int j = 0; j <= n; j ++)
            printf("%d %d %d\n", i, j, f[i][j]);
    }
}
deque  Q;
void initQ(){
    while(!Q.empty())
        Q.pop_back();
}
int main(){
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= k; i ++)
        scanf("%d %d %d", &w[i].l, &w[i].p, &w[i].s);
    sort(w+1, w+k+1, cmp);  //别忘了先按S排序!
    for (int i = 1; i <= k; i ++){
        for (int j = 0; j < w[i].s; j ++)
            f[i][j] = max(f[i-1][j], f[i][j-1]);
        //单调队列
        initQ();
        for (int j = max(0, w[i].s - w[i].l); j < w[i].s; j ++){
            while(!Q.empty() && f[i-1][Q.back()] - Q.back()*w[i].p <= f[i-1][j] - j*w[i].p)
                Q.pop_back();
            Q.push_back(j);
        }
        for (int j = w[i].s; j < min(w[i].s + w[i].l, n+1); j ++){
            while (!Q.empty() && Q.front() < j - w[i].l)
                Q.pop_front();
            f[i][j] = max(f[i-1][j], f[i][j-1]);
            f[i][j] = max(f[i][j], f[i-1][Q.front()] - Q.front()*w[i].p + j * w[i].p);
//            朴素DP:
//            for (int p = max(0, j - w[i].l); p <= j; p ++)
//                f[i][j] = max(f[i][j], f[i-1][p] + (j - p)*w[i].p);
        }
        for (int j = w[i].s + w[i].l; j <=n; j ++)
            f[i][j] = max(f[i-1][j], f[i][j-1]);
    }
    debug();
    int res = 0;
    for (int j = 1; j <= n; j ++)
        res = max(res, f[k][j]);
    printf("%d\n", res);
	return 0;
}
 

转载于:https://www.cnblogs.com/AbandonZHANG/archive/2013/02/21/4114223.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值