zoukankan      html  css  js  c++  java
  • 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;
    }
    
     
    举杯独醉,饮罢飞雪,茫然又一年岁。 ------AbandonZHANG
  • 相关阅读:
    软件工程第二次作业
    软件工程第一次作业
    软件工程最后一次作业
    软件工程第四次作业
    软件工程第三次作业
    软件工程第二次作业
    软件工程第一次作业
    《算法笔记》2.2小节——C/C++快速入门->顺序结构
    大数阶乘
    printf("%f ",5)的输出结果为什么是0.000000
  • 原文地址:https://www.cnblogs.com/AbandonZHANG/p/4114006.html
Copyright © 2011-2022 走看看