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 ;
}