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;
}
本文介绍了一种优化动态规划算法解决特定分组问题的方法。问题要求将一系列数字分为若干组,每组数字之和不超过限定值M,目标是最小化各组最大值之和。文章详细探讨了传统DP方法的局限性,并提出使用单调队列和BST树来优化算法,最终实现O(N*logN)的时间复杂度。
567

被折叠的 条评论
为什么被折叠?



