poj 3245 Sequence Partitioning dp+二分+单调队列

题目链接:

http://poj.org/problem?id=3245

题目大意:

有n个连续的数对(Ai,Bi).求一个划分,使得每一部分的B的和的最大值最小。

划分要求:

1、对于任意的p<q , p,q属于不同的部分,则有Bp>Aq.

2、对于每一部分的最大A的和小于给定的lim.

解题思路:

当p<q是如果Bp<=Aq则p,q一定是属于一个部分。

先把必须在一起的粗略划分下,然后二分B的和的最小值。

dp[i]表示到达第i个数对时,此时的最大的A的和。

dp[i]=min{dp[j]+max(Ak,j+1<=k<=i),其中b(j+1)+...+b(i)<=二分的值。

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF 0x1f1f1f1f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
using namespace std;

/*
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
*/
#define M 55000
int n,lim,a[M],b[M],p[M],q[M]; //p[i]表示将b数组从小到大排序后的下标,
//q[i]表示b中的第i号元素与a中的q[i]号元素可能有冲突
int dp[M]; //dp[i]表示到达第i个数对时,a中能达到的最小值

bool cmp(int i,int j)
{
   return b[i]<b[j];  //按b数组排序,将下标对应交换
}

multiset<int>sbt; //多重集合容器,里面的存储结构是平衡二叉树
bool iscan(int m)
{
   int p=1,r=-1,le=0;
   ll sum=0;
   sbt.clear();

   for(int i=1;i<=n;i++)
   {
      sum+=b[i];
      while(sum>m)
         sum-=b[p++];
      if(p>i)
         return false; //说明有单个的值超过了m
      while(le<=r&&a[i]>=a[q[r]]) //去掉后面的小的
      {
         if(le<r)
            sbt.erase(dp[q[r-1]]+a[q[r]]);  //注意使得A的最大值和最小时,
                                          //可以以最大的a[i]为分界点
         r--;
      }
      q[++r]=i;
      if(le<r) //如果队列至少有两个元素
         sbt.insert(dp[q[r-1]]+a[q[r]]);
      while(le<=r&&q[le]<p)
      {
         if(le<r)
            sbt.erase(dp[q[le]]+a[q[le+1]]);
         le++;
      }
      dp[i]=a[q[le]]+dp[p-1];
      ll tmp=*sbt.begin();
      if(le<r&&tmp<dp[i]) //队列中至少有两个元素
         dp[i]=tmp;
   }
   return dp[n]<=lim;
}

int main()
{
   while(scanf("%d%d",&n,&lim)!=EOF)
   {
      for(int i=1;i<=n;i++)
      {
         scanf("%d%d",&a[i],&b[i]);
         q[i]=p[i]=i;
      }
      sort(p+1,p+n+1,cmp);
      for(int i=n,j=1;i>=1;i--)
         while(j<=n&&b[p[j]]<=a[i]) //将b中与a冲突的标记
               q[p[j++]]=i;         //将b全部用完,尽量去消耗下标值大的,
                                    //小的不用管了,找到最大冲突区间

      int le,ri,j=1;
      for(int i=1;i<=n;i=le,j++)  //有冲突一定要放在一起
      {
         a[j]=a[i],b[j]=b[i];
         for(le=i+1,ri=max(q[i],i);le<=ri;le++)
         {
            a[j]=max(a[j],a[le]);
            b[j]+=b[le];
            ri=max(ri,q[le]); //又产生了新的冲突
         }
      }
      n=j-1;//现在一共只有j-1组了
      le=1,ri=((1<<31)-1);
      //printf("%d\n",ri);
      int m,ans;

      while(le<=ri)
      {
         m=(le+ri)>>1;
         if(iscan(m))
            ans=m,ri=m-1; //能否更小
         else
            le=m+1;//不行的话,增大
      }
      printf("%d\n",ans);
   }
   return 0;
}







你可能感兴趣的:(poj 3245 Sequence Partitioning dp+二分+单调队列)