http://acm.hdu.edu.cn/showproblem.php?pid=3872
题意:
有N个球,每个球都有一个type和energy,现在要求将N个球分成若干组,每个组的中要求没有和最右边的球一样type的球,每个组的得分是该组中所有球的最大值, 求所有组的最小得分。
思路:
这个题目dp的状态很好表示,用Fi表示前i个球分成若干组之后的最小得分,状态转移方程就是:Fi = Fj + max{ j+1 ... i } ,其中 pre[i] <= j < i ,但是如果直接去枚举j,复杂度就会达到O(N^2),肯定会超时。这就说明我们需要去优化这个dp的决策点的选择,我们注意到对于一个固定的i,j的决策点是稀疏的,这样我们就可以用一个单调队列来维护energy 的单调递减的性质,对于队列中的任意两个数的之间的决策点来说,max{j+1 .. i } 就是一定的,那么我们只需要求出该区间内的最小dp值就可以了。这样我们的思路就很清楚了,先处理出每个位置最左边的决策位置pre[i], 然后二分找到pre[i]在单调队列中的区间,对于这个区间右边的区间都是最值可能存在的地方,于是我们可以用一棵线段树来维护区间内dp的最小值,同时也维护单调队列中位置中的最小值,这样边求解边维护线段树就可以在O(nlogn)的时间内求解本题。
代码:
#include <stdio.h>
#include <string.h>
typedef __int64 LL ;
const LL inf = 10000000000000000LL ;
const int MAXN = 100010 ;
LL N ;
LL tt[MAXN], ee[MAXN] ;
LL pre[MAXN] , hash[MAXN] ;
LL dp[MAXN] ;
LL que[MAXN] , top ;
LL min1[MAXN<<2] , min2[MAXN<<2] ;
void init(){
scanf("%I64d",&N);
for(int i=1;i<=N;i++) scanf("%I64d",&tt[i]) ;
for(int i=1;i<=N;i++) scanf("%I64d",&ee[i]) ;
memset( hash , -1, sizeof(hash) );
for(int i=1;i<=N;i++){
if( hash[ tt[i] ] == -1 ) pre[i] = 0 ;
else pre[i] = hash[ tt[i] ] ;
hash[ tt[i] ] = i ;
}
}
LL find(LL l , LL r, LL val ){
while( l < r ){
LL mid = (l + r ) >> 1 ;
if( que[mid] >= val ) r = mid ;
else l = mid + 1 ;
}
return l ;
}
void up2(int idx){
LL ls = idx<<1 , rs = idx<<1|1 ;
min2[idx] = min2[ls] > min2[rs] ? min2[rs] : min2[ls] ;
}
void update2(LL l , LL r, LL idx, LL pos , LL val){
if(l == r){
min2[idx] = val ;
return ;
}
LL mid = (l + r) >> 1 , ls = idx<<1 , rs = idx<<1|1 ;
if( pos <= mid ) update2( l , mid , ls, pos , val ) ;
else update2( mid+1, r , rs , pos , val ) ;
up2( idx );
}
void build(LL l , LL r, LL idx){
min1[idx] = min2[idx] = inf ;
LL mid = (l + r) >> 1 , ls = idx<<1 ,rs = idx<<1|1;
if(l == r) return ;
build(l , mid , ls) ; build( mid+1, r, rs) ;
}
LL query1(LL l ,LL r, LL idx , LL a, LL b){
if( l==a && r==b){
return min1[idx] ;
}
LL mid = (l + r) >> 1 , ls = idx<<1 ,rs = idx<<1|1 ;
if( b<=mid ) return query1( l , mid , ls , a ,b ) ;
else if( mid < a ) return query1( mid+1 , r ,rs , a ,b ) ;
else{
LL aa = query1( l , mid , ls , a, mid) ;
LL bb = query1( mid+1, r, rs, mid+1, b) ;
return aa > bb ? bb : aa ;
}
}
void up1(LL idx){
LL ls = idx<<1 , rs = idx<<1|1 ;
min1[idx] = min1[ls] > min1[rs] ? min1[rs] : min1[ls] ;
}
void update1(LL l ,LL r, LL idx, LL pos , LL val){
if(l == r){
min1[idx] = val ; return ;
}
LL mid = (l + r) >> 1 ,ls = idx<<1 , rs = idx<<1|1 ;
if( pos<=mid ) update1(l , mid, ls , pos, val );
else update1(mid+1 , r , rs , pos , val) ;
up1( idx ) ;
}
LL query2(LL l ,LL r, LL idx, LL a, LL b){
if(l==a && r==b){
return min2[idx] ;
}
LL mid = (l + r) >> 1 , ls = idx<<1 ,rs = idx<<1|1 ;
if( b<=mid ) return query2( l , mid , ls , a , b) ;
else if( mid<a ) return query2( mid+1, r , rs , a ,b );
else{
LL aa = query2(l , mid , ls , a , mid ) ;
LL bb = query2(mid+1, r, rs, mid+1, b);
return aa > bb ? bb : aa ;
}
}
void solve(){
LL a ,res1 ,res2 ;
dp[0] = 0 ;
dp[1] = ee[1] ; top = 0 ;
que[ top++ ] = 1 ;
build(0 , N , 1) ;
update2( 0 , N , 1 , 0 , ee[1] ) ;
update1( 0 , N , 1 , 1 , dp[1] ) ;
update1( 0 , N , 1 , 0 , dp[0] ) ;
for(LL i=2;i<=N;i++){
while( top ){
a = que[top-1] ;
if( ee[a] < ee[i] ){
update2(0 , N , 1 , top-1 , inf ) ;
top-- ;
}
else break ;
}
if( top ){
a = que[top-1] ;
res1 = query1(0 , N , 1 , a, i-1 );
}
else
res1 = 0 ;
res1 = res1 + ee[i] ;
que[top++] = i ;
update2(0 , N , 1 , top-1 , res1 ) ;
LL s = pre[i] + 1 , e = i ;
LL ppp = find( 0 , top-1 , s ) ;
res1 = query1(0 , N , 1 , pre[i] , que[ppp]-1) ;
res1 += ee[ que[ppp] ] ;
if( ppp+1<top ){
res2 = query2( 0 , N , 1 , ppp+1 , top-1 ) ;
res1 = res1 < res2 ? res1 : res2 ;
}
dp[i] = res1 ;
update1( 0 , N ,1 ,i , dp[i] ) ;
}
printf("%I64d\n",dp[N]);
}
int main(){
int T; scanf("%d",&T);
while( T-- ){
init() ;
solve() ;
}
return 0 ;
}