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