zoukankan      html  css  js  c++  java
  • bzoj2448 挖油

    2448: 挖油

    Time Limit: 10 Sec  Memory Limit: 128 MB
    Submit: 140  Solved: 61
    [Submit][Status][Discuss]

    Description

    给出一条线段,在左端点点0与右端点n+1间有n个点(n<=2000),并且在0到x之间的所有点都是有油的,在每个点钻井判断是否有油需要时间ti,求能够知道x的最坏情况下最少需要多少时间。

    Input

    第一行包含一个数n,如题目描述。
    第二行包含n个数,表示在第i个点钻井判断是否有油需要的时间。

    Output

    输出包含一行,最坏情况下最少需要多少时间。

    Sample Input

    4
    8 24 12 6

    Sample Output

    42

    HINT

    对于100%的数据,n<=2000,ti<=10^6

    Source

    2011福建集训

    分析:个人认为比较神的一道题,如果之前没有接触过这类题根本无从下手的那种.

       做这道题之前可以先做一下poj3783,都是要使得最大值最小,猜答案的那种.

       想法还是dp,能不能直接拿poj3783的状态来用呢?显然不行,这道题的答案和编号还是有关的,而poj3783是无关的.

       类似的分析方法:先考虑在一个点k钻井,因为要使得结果最坏,如果k不是两个区间的端点,都不能知道答案,需要继续.这个时候就有两种选择了:1.往左. 2.往右. 每次取max,直到到达边界.  这个过程很像区间dp,有左右端点的限制嘛.那么令f[i][j]表示区间

    [i,j]最坏情况下需要的最少时间.转移就是枚举一个点k,f[i][j] = min{max{f[i][k - 1],f[k + 1][j]} + a[k]}. 初始化f[i][i] = a[i].

       这个dp的想法就是每次考虑当前钻哪个井,结果最坏就必须要钻其它位置的井,由此来扩展,取max是为了得到最坏情况.

       区间dp一般都是O(n^3)的,这道题也不例外. 怎么优化呢?括号内的max限制了f[i][j]从哪个状态转移过来.如果没有了max,方程就变成了一个递推式.

       考虑如何去掉max.可以发现当k大到一定程度的时候,f[i][k - 1]一定大于f[k + 1][j]的,那么就只会从f[i][k - 1]转移过来,因为f[i][k - 1]随着k的增大是单调不减的,所以可以用很多效率高的方法维护,一个比较好的方法就是单调队列. 每次将f[i][k-1]和a[k]的整体存到单调队列中,弹出最小值即可. 对于j也一样.

       现在的问题是如何找到这个分界点.一个结论:区间[i,j + 1]的分界点一定在区间[i,j]的分界点右边,或者相同.这个挺好想的,因为左右两边的转移实际上是一种竞争关系嘛,如果分界点不增加,那么右边的值就会增大. 分界点的位置是单调的了,那么固定i,枚举j的时候,分界点也是在右移的,只需要维护一个指针表示分界点的位置就好了. 如果左边决策比不上右边决策,则指针往右.

       上面考虑的是固定i,每次从左区间转移的答案.  固定j也是一样的,只是变成了指针向左扫. 这样的话有一个问题:每枚举到1个i,就要从i+1到n枚举j,难道每次都要清空优先队列吗? 不需要!

       区间dp为了先处理得到小区间的值,倒序枚举i,顺序枚举j,对于j的单调队列不需要清空(i只会从上一个i的位置向左移动),为了避免重复占用同一个单调队列,开n个单调队列,而对于i的则必须清空了.  用一个单调队列维护固定i的答案,n个单调队列维护固定j的答案,总共就是n+1个单调队列了.

       Question:单调队列维护的都是队首元素最小的队列. 最后的答案也是在两个单调队列中取min.那为什么没有max呢? 因为max只是决定了从哪个状态转移而来,最后对答案有影响的主要还是取min的部分,取max的部分在维护两个指针的过程中已经考虑了.

       这类题型要熟记,对于决策会有分界点的dp优化也要熟练掌握.

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int maxn = 2010;
    int n,a[maxn],f[maxn][maxn],l[maxn],r[maxn],q[maxn][maxn];
    
    int cal1(int x,int y,int z)
    {
        return f[x][z - 1] + a[z];
    }
    
    int cal2(int x,int y,int z)
    {
        return f[z + 1][y] + a[z];
    }
    
    int main()
    {
        memset(f,127/3,sizeof(f));
        scanf("%d",&n);
        for (int i = 1; i <= n; i++)
            scanf("%d",&a[i]);
        for (int i = n; i >= 1; i--)
        {
            f[i][i] = a[i];
            l[0] = 0;
            r[0] = 1;
            q[0][++r[0]] = i;
            for (int j = i + 1; j <= n; j++)
            {
                while (l[0] <= r[0] && cal1(i,j,q[0][l[0]]) < cal2(i,j,q[0][l[0]]))  //这实际上就是维护一个指针
                    l[0]++;
                while (l[0] <= r[0] && cal1(i,j,j) < cal1(i,j,q[0][r[0]]))
                    r[0]--;
                q[0][++r[0]] = j;
                while (l[j] <= r[j] && cal2(i,j,q[j][l[j]]) < cal1(i,j,q[j][l[j]]))
                    l[j]++;
                while (l[j] <= r[j] && cal2(i,j,i) < cal2(i,j,q[j][r[j]]))
                    r[j]--;
                q[j][++r[j]] = i;
                f[i][j] = min(cal1(i,j,q[0][l[0]]),cal2(i,j,q[j][l[j]]));
            }
        }
        printf("%d
    ",f[1][n]);
    
        return 0;
    }
  • 相关阅读:
    【转】23种设计模式详解
    JavaScript 中的对象引用
    iOS网络请求之NSURLSession
    学习编程是否做笔记的思考
    汇编语言——统计一个字符串中的大写字母、小写字母、数字和其他字符的个数,并显示
    windows7 64位如何调出debug
    Code:Blocks编写后出现如下错误
    iOS中Git的使用
    任意定义一个二维数组,实现矩阵的转置——java
    将八进制数转换为十进制数——java
  • 原文地址:https://www.cnblogs.com/zbtrs/p/8491115.html
Copyright © 2011-2022 走看看