POJ_3017 Cut the Sequence 单调队列+dp+BST

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

题意:

给你一串数字,要求将数字分成若干组, 每组中的数字的sum不能超过M,求所有分组

中,每组的最大值的和的最小值。N<=100000

思路:

本题dp的思路是很容易就可以想到的,F( i ) 表示将1到i号元素分成若干组之后,每组的

最大值的和的最小值,转移方程为:F( i ) = min{ F( K ) + max{ K+1 , i }  },但是考虑到该

方程的复杂度,就让我们不得不放弃这种做法,复杂度为:O(N*N)肯定会超时,这样就

需要优化。首先仔细分析之后,我们就会发现,对于一个i, 可能成为决策点的k必须满

足的条件是:val[k] > val[j] ,k+1<=j<=i,下面给出一个简单的证明:首先我们假设上述

条件不满足,也就是说,有一个j满足val[k] <= val[j],由于我们知道dp[i] 是随着i的增加而

不减的,所以,我们完全可以将决策点往前推,也就是说将决策点的下标减小,知道减到

一个k' ,使得val[k'] > val[j],这时候的决策点k'绝对要比k优(我们这里先不考虑M的限制条

件),所以我们就证明了,能成为决策点的必要条件。这样我们就可以将所有小于等于i的

备选决策点放入一个单调递增的单调队列,每次我们只要从队尾插入,从对头删除不符合

要求的决策点就可以了。

但是这里还有一个问题,我们只是推出了决策点的必要条件,但是并不能保证从对头

得到的第一个元素就是最优决策点,但是我们可以保证的是最优决策点一定在单调队列中,

因此这里我们可以用一个BST来维护最优决策点,每个点最多进一次BST树,复杂度为:

O(N*logN),单调队列的dp复杂度为:O(N),这样总的复杂度就是O(N*logN),本题得以解

决。


代码:

#include<stdio.h>
#include<string.h>
#include<time.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
typedef long long LL ;
const int MAXN = 100010 ;
LL N ,M ;
LL val[MAXN] , sum[MAXN] ;
int que[MAXN],front ,rear ;
int b[MAXN] ;
LL dp[MAXN] ;

struct Node{
    LL key ,pri ;
    Node *ch[2] ;
}*root,tree[MAXN*2] ;
int tot ;

inline LL MIN(LL a , LL b){
    return a > b ? b : a ;
}
void search(int i){
    int low, high , mid ;
    low = 0 ; high = i ;
    while(low < high){
        mid = (low + high) >> 1 ;
        if( sum[i]-sum[mid] > M ){
            low = mid + 1;
        }
        else    high = mid ;
    }
    b[i] = low ;
}
void cal(){
    b[0] = 0 ;
    int i ;
    for(i=1;i<=N;i++){
        if(sum[i] <= M )    b[i] = 0 ;
        else        break ;
    }
    for( ; i<=N ; i++ ){
        search( i );
    }
}
Node *get(Node *n1, Node *n2){
    if( n1==NULL )  return n2 ;
    else            return n1 ;
}
void rotate(Node* &now,int t){
    Node *p = now->ch[t] ;
    now->ch[t] = p->ch[t^1] ;
    p->ch[t^1] = now ;
    now = p ;
}
void remove(Node* &now , LL x){
    if(now == NULL) return ;
    if( now->key != x ){
        int t = x > now->key ;
        remove( now->ch[t] , x);
    }
    else{
        if( now->ch[0]==NULL || now->ch[1]==NULL ){
            now = get( now->ch[0] , now->ch[1] ) ;
        }
        else{
            int t = now->ch[1]->pri > now->ch[0]->pri ;
            rotate( now , t );
            remove( now->ch[t^1] , x);
        }
    }
}
Node* new_node( LL v ){
    tot++ ;
    Node *p = &tree[tot] ;
    p->ch[0] = p->ch[1] = NULL ;
    p->key = v ;
    p->pri = rand() * rand() ;
    return p ;
}
void insert(Node* &now , Node* x ){
    if(now == NULL) now = x ;
    else{
        int t = now->key < x->key ;
        insert( now->ch[t] , x);
        if( now->ch[t]->pri > now->pri) rotate( now , t ) ;
    }
}
Node *find(Node *now){
    Node *p = now , *q = now ;
    while(p != NULL){
        q = p ;
        p = p->ch[0] ;
    }
    return q ;
}
void DP(){
    front = rear = 0 ;
    for(int i=1;i<=N;i++)
        dp[i] = (1<<30) ;
    dp[0] = 0 ;
    que[rear++] = 0 ;
    root = NULL ; tot = 0  ;

    for(int i=1;i<=N;i++){
        while(front < rear){
            int a = que[rear-1] ;
            if( val[a] <= val[i] ){
                rear -- ;
                if( front < rear){
                    int b = que[rear-1] ;
                    LL res = val[a] + dp[b] ;
                    remove( root , res );
                }
            }
            else    break ;
        }
        que[rear++] = i ;
        if(front+1 < rear){
            int a = que[rear-1] ;
            int b = que[rear-2] ;
            LL res = dp[ b ]+ val[ a ] ;
            Node *x = new_node( res ) ;
            insert( root , x );
        }
        while(front < rear ){
            int a = que[front] ;
            if( a <= b[i] ){
                front++ ;
                if(front < rear){
                    int b = que[front] ;
                    LL res = dp[a] + val[b] ;
                    remove( root, res) ;
                }
            }
            else        break ;
        }
        if(front < rear)
            dp[i] = MIN( dp[i] , dp[ b[i] ] + val[ que[front] ]);
        Node *p = find(root);
        if(p != NULL){
            dp[i] = MIN( dp[i] , p->key );
        }
    }
    printf("%lld\n",dp[N]) ;
}
int main(){
    srand( time(NULL) );
    while(scanf("%lld %lld",&N,&M) == 2){
        sum[0] = 0 ; bool ok = 1 ;
        for(int i=1;i<=N;i++){
            scanf("%lld",val+i);
            if( val[i] > M )    ok = 0 ;
            sum[i] = sum[i-1] + val[i] ;
        }
        val[0] = (1<<30) ;
        if( !ok ){
            printf("-1\n")  ; continue ;
        }
        cal() ;
        DP() ;
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值