您的位置:首页 > 产品设计 > UI/UE

Cut the Sequence,Sequence Partitioning,POJ3017,POJ3245,用单调队列优化的DP

2013-07-16 21:46 357 查看
为什么这两题要放到一起说呢,主要是这两题十分类似,用单调队列优化的方法是一样的,所以放在一起总结会比较印象深刻。

先说POJ3017----------Cut the Sequence

题目地址:http://poj.org/problem?id=3017

题目意思:

给你N个数和一个值M

可以将这N个数分成任意段,使得每段的和不大于M

要你使得每段的最大值加起来的和最小,求这个最小值

解题思路:

首先咱们提出一个状态转移方程:

f[i] = f[b[i]-1] + max(a[k]) b[i]<=k<=i

其中b[i]表示的是i属于的这块的头部,f[i]表示的以i结尾的块到目前为止的最大值的和

那么我们要求的和就是f

但是这个复杂度太大了,我们得想想优化方法

注意到要求某一个窗口的最大值

那么咱们就用一个优先队列来维护一个最大值队列

则,就可以得到一个可行解f[i] = f[p-1]+a[q[l]],p就是分到i的这块的块头,而a[q[l]]就是这个优先队列的队首

但是这个不一定是最优解,可能咱们这个分块不一定是最科学的,那么我们就要从前面的结果中去找最优解来和这个可行解进行比较

如果遍历的话复杂度是O(n),但是如果用BST,可以把复杂度降一降,即O(logn)

那么最优解会是什么形式呢?

对于优先对列的q[j]来说,应该是f[q[j]]+a[q[j+1]],即a[q[j]]一定比后面的大,如果不是比后面的大,那我们可以把a[q[j]]划分到后面的这个块中,反正不影响,也许还可以找到更优的解(此中有真意,要仔细的思考思考)

那么确定了之后,我们怎么保证每段的和小于等于M,可以用下面的代码:

while(sum>m)
                sum-=a[p++];
if(p>i) break;
这个p我们已经在之前的提到了,p就是i的这块的块头

如果还有不清楚的就看看代码:(这题不知为啥,用多case的数据处理方式就报WA,看来POJ的BUG还是很多啊)

#include<cstdio>
#include<cstring>
#include<set>
using namespace std;

#define LL long long

const int maxn = 111111+10;

int a[maxn],q[maxn];
LL sum,m,f[maxn],tmp;
int n;
multiset<int> bst;

int main()
{
    //while(~scanf("%d%I64d",&n,&m))
    scanf("%d%I64d",&n,&m);
    {
        int l,r,p;
        l=0;
        r=-1;
        f
=-1;
        p=1;
        f[0]=0;
        sum=0;
        bst.clear();

        //这个是为了满足每块的和要小于m
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            sum+=a[i];
            while(sum>m)
                sum-=a[p++];
            if(p>i) break;

            //要保证队列的单调性
            while(l<=r && a[i]>=a[q[r]])
            {
                if(l<r)//删除队尾,就要删除由它相应产生的最优解
                    bst.erase(f[q[r-1]]+a[q[r]]);
                r--;
            }
            q[++r] = i;

            //添加一个放到集合里面去,有可能是最优解
            if(l<r) bst.insert(f[q[r-1]]+a[q[r]]);

            //根据前面求出的p,必须相应的后移队首,因为队首已经不能在优先队列中了
            while(q[l]<p)
            {
                if(l<r) bst.erase(f[q[l]]+a[q[l+1]]);
                l++;
            }

            f[i] = f[p-1]+a[q[l]];//由队首产生的一组可行解,不一定是最优解

            tmp = *bst.begin();

            if(l<r && f[i]>tmp)
                f[i] = tmp;
        }
        printf("%I64d\n",f
);
    }
    return 0;
}


======================分割线==================================
说完了上面的,再说下面的就容易多了

即POJ3245-------Sequence Partitioning

题目地址:http://poj.org/problem?id=3245

题目意思:

还是给你N个数对,一个限定值LIMIT

首先是把这N个数分成任意段,但是有要求

对于第p,q块,p<q

则Bp>Aq

此外,所有块的A最大值之和要小于LIMIT(是不是似曾相识?)

然后要你求出对于每块来说,B有一个和,要使所有块的和的最大值尽量小,求出这个值

首先来分块,有一些是必须要分的

对于i<j来说,如果Bi<Aj的话,那么从i~j必须和到一块去,这个是必须的,那么怎么做到这步呢

咱们可以先把B按从小到大排序,然后把A从N往前枚举,把B从排好的序往前枚举

只要B小于A,那么这个B是肯定要和A放到一起的,标记对打编号就OK,这也是A要倒着枚举的原因

具体的看代码

第一阶段的分块完成之后,我们就可以枚举这个要求的值,也就是POJ3017里面的M值

然后判断可行性就OK了,判断的代码和POJ3017很类似

所以这题相当于是两次分块

第一次是根据第一条件分了必须块,第二个就是根据枚举的M来调整,并判断可行性

下面上代码:

#include<cstdio>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;

const int maxn = 50000+100;

int a[maxn],b[maxn],q[maxn],f[maxn],p[maxn];
int n,limit,sum,tmp;
multiset<int> bst;

bool cmp(int v,int u)
{
    return b[v]<b[u];
}

bool check(int m)
{
    int l,r;
    l=f[0]=0;
    f
=r=-1;
    int pos=1;
    sum=0;
    bst.clear();

    for(int i=1;i<=n;i++)
    {
        sum+=b[i];
        while(sum>m)
            sum-=b[pos++];
        if(pos>i)
            return false;
        while(l<=r && a[i]>=a[q[r]])
        {
            if(l<r)
                bst.erase(f[q[r-1]]+a[q[r]]);
            r--;
        }

        q[++r] = i;
        if(l<r) bst.insert(f[q[r-1]]+a[q[r]]);

        while(q[l]<pos)
        {
            if(l<r)
                bst.erase(f[q[l]]+a[q[l+1]]);
            l++;
        }

        f[i] = f[pos-1]+a[q[l]];

        tmp = *bst.begin();

        if(l<r && f[i]>tmp)
            f[i]=tmp;
    }

    return f
<=limit;
}

int main()
{
    scanf("%d%d",&n,&limit);

    int i,j;

    for(i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i],&b[i]);
        q[i]=p[i]=i;
    }

    sort(p+1,p+1+n,cmp);

    for(j=1,i=n;i>0;i--)
    {
        while(j<=n && b[p[j]]<=a[i])
            q[p[j++]]=i;
    }

    int l,r;

    for(i=1,j=1;i<=n;i=l,j++)
    {
        a[j]=a[i],b[j]=b[i];
        for(l=i+1,r=max(q[i],i);l<=r;l++)
        {
            a[j] = max(a[j],a[l]);
            b[j] +=b[l];
            r=max(r,q[l]);
        }
    }

    l=0,r=0x3f3f3f3f;
    int ans;
    n=j-1;

    while(l<=r)
    {
        int mid = (l+r)>>1;
        if(check(mid))
            ans=mid,r=mid-1;
        else
            l=mid+1;
    }

    printf("%d\n",ans);

    return 0;
}


另外要感谢:http://blog.csdn.net/fp_hzq,我这两题都是根据他的博客学的,

详见/article/2371441.html/article/2371447.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: