zoukankan      html  css  js  c++  java
  • POJ1821 Fence

    传送门

    这道题是一道很好的单调队列优化DP的例子。

    题目大意是有n个工人,每个人可以粉刷一段长度不超过l[i]的墙,如果一个人粉刷了那么他必须要粉刷第s[i]块墙,一个人粉刷一块墙能得到p[i]的钱,求所有工人得到的钱的最大值。

    我们首先把所有工人按s[i]排序,这样方便我们线性DP。

    考虑DP,用dp[i][j]表示前i个工人刷j块的得到的钱的最大值,那么我们分类讨论,首先是一个人不刷和一块墙不刷的情况,那么就有dp[i][j] = max(dp[i-1][j],dp[i][j-1]);

    之后,我们考虑这个工人刷墙的情况。因为这个人必须要刷s[i],那这个人的转移必然是从s[i] - l[i] ~ s[i]这一段转移过来的。否则的话就成了不合法情况。那么就有dp[i][j] = max{dp[i-1][k] + (j - k) * p[i]} (s[i] - l[i] <= k <= s[i])

    这个式子我们朴素的做法是先枚举i,对于每一个工人i,枚举它粉刷的块数j,之后再枚举转移的范围。不过这样会超时,我们考虑优化。我们发现如果把j,k分离出来,那么对于每一个j(状态),它对应的j * p[i]是一个定值,只有内部的式子随着k(决策)的变化而改变,而且其实对于每一个j,有很大一部分的可选取的决策区间是重复的。

    说到这里,我们就想到用单调队列维护啦!!那么我们的想法就是,对于每一个工人,我们首先把s[i] - l[i] ~ s[i]这一段区间之内的所有决策压入单调队列,之后随着j的增加,所能选取的k的区间左端点随之变大。也就是说实际上,我们只要维护一段左端点变大,右端点不动的区间最大值就可以了。

    所以一般来说,单调队列优化DP的套路就是,如果一个DP方程能被拆成每一个状态都是从一个决策区间中选取最优的转移,然后决策区间随着状态变化左右端点变化,而且有大量重复,我们就可以先拆分式子,拆成内层只与决策有关,这样直接用单调队列维护决策的极值,就省去了一层循环,优化了时间。

    看一下代码。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #include<cmath>
    #include<set>
    #include<queue>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    typedef long long ll;
    const int M = 10005;
    const int INF = 1000000009;
    
    int read()
    {
       int ans = 0,op = 1;
       char ch = getchar();
       while(ch < '0' || ch > '9')
       {
          if(ch == '-') op = -1;
          ch = getchar();
       }
       while(ch >= '0' && ch <= '9')
       {
          ans *= 10;
          ans += ch - '0';
          ch = getchar();
       }
       return ans * op;
    }
    
    struct worker
    {
       int l,p,s;
       bool operator < (const worker &g) const
       {
          return s < g.s;
       }
    }a[105];
    
    int n,k,dp[105][20005],q[20005],head,tail;
    
    int calc(int i,int x)
    {
       return dp[i-1][x] - x * a[i].p;
    }
    
    int main()
    {
       n = read(),k = read();
       rep(i,1,k) a[i].l = read(),a[i].p = read(),a[i].s = read();
       sort(a+1,a+1+k);
       rep(i,1,k)
       {
          head = 1,tail = 0;
          rep(j,max(0,a[i].s-a[i].l),a[i].s-1)
          {
         while(head <= tail && calc(i,q[tail]) < calc(i,j)) tail--;
         q[++tail] = j;
          }
          rep(j,1,n)
          {
         dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
         if(j >= a[i].s)
         {
            while(head <= tail && q[head] < j - a[i].l) head++;
            if(head <= tail) dp[i][j] = max(dp[i][j],calc(i,q[head]) + j * a[i].p);
         }
          }
       }
       printf("%d
    ",dp[k][n]);
       return 0;
    }
  • 相关阅读:
    jchdl
    jchdl
    UVa 10256 (判断两个凸包相离) The Great Divide
    UVa 11168 (凸包+点到直线距离) Airport
    LA 2572 (求可见圆盘的数量) Kanazawa
    UVa 10652 (简单凸包) Board Wrapping
    UVa 12304 (6个二维几何问题合集) 2D Geometry 110 in 1!
    UVa 10674 (求两圆公切线) Tangents
    UVa 11796 Dog Distance
    LA 3263 (平面图的欧拉定理) That Nice Euler Circuit
  • 原文地址:https://www.cnblogs.com/captain1/p/9929440.html
Copyright © 2011-2022 走看看