题目链接:http://lightoj.com/volume_showproblem.php?problem=1415
题意:一排树,有两个属性,类型和高度。将其分成若干组:(1)每组必须是连续的若干棵树;(2)每组内不允许有相同类型的树;(3)每组内的代价为该组内最高的那棵树的高度。求一种分组方式使得总代价最小。
思路:(1)首先我们求出第i棵树分组的最 左位置left[i],设第i棵树的类型为type[i],type[i]上一次的位置为pos[type[i]],则 left[i]=max(pos[type[i]]+1,left[i-1])。这个是显然的,比如类型依次为3 2 1 2 3,那么我们计算第5个3的left值时,首先判断前一个3的位置1,所以这个left[5]必然是大于等于2的。再看前一个位置的2,其left值为 3,那么此left[5]=3。
(2)接着,我们求出第i棵树前面比其小的第一个树的位置,即最右位置rigth[i]。这个用一个单调栈求出。每次将栈顶比其小的删掉,直到栈顶的数大于等于这个数,然后这个栈顶的这个数的位置+1就是这个right值。
(3)用f[i]表示前i个的最小代价,那 么f[i]=min(f[j-1]+max(height[j,i]))。设对于当前的 i,g[j]=max(height[j,i]),h[j]=f[j-1],那么f[i]=min(g[j]+h[j])。我们用线段树维护这个最小值。 那么在DP完i后,在DPi+1时,要将right[i+1]到i+1之间的g值修改为height[i+1](height[i+1]为i+1位置的树 的高度)。然后为了修改g后能统计出min(g[j]+h[j]),我们还要保存区间的最小h值。
struct node { int type,h,x,y; void get() { RD(type,h); } }; node a[N]; int b[N],c[N],n; void init() { clr(b,0); int i,t; FOR1(i,n) { t=a[i].type; a[i].x=max(b[t]+1,a[i-1].x); b[t]=i; } int top=0; b[top]=INF; c[top]=0; FOR1(i,n) { t=a[i].h; while(b[top]<t) top--; a[i].y=c[top]+1; b[++top]=t; c[top]=i; } } struct Node { int L,R,mid,flag; i64 g,h,Min,minH; void set(int x) { Min=minH+x; g=x; flag=1; } }; Node d[N<<2]; void pushUp(int t) { if(d[t].L==d[t].R) return; int x=t*2,y=t*2+1; d[t].Min=min(d[x].Min,d[y].Min); d[t].minH=min(d[x].minH,d[y].minH); } void pushDown(int t) { if(d[t].L==d[t].R) return; if(!d[t].flag) return; d[t].flag=0; d[t*2].set(d[t].g); d[t*2+1].set(d[t].g); } void build(int t,int L,int R) { d[t].L=L; d[t].R=R; d[t].mid=(L+R)>>1; d[t].flag=0; d[t].minH=d[t].Min=d[t].g=d[t].h=inf; if(L==R) return; build(t*2,L,d[t].mid); build(t*2+1,d[t].mid+1,R); pushUp(t); } void setH(int t,int pos,i64 h) { if(d[t].L==d[t].R) { d[t].minH=d[t].h=h; d[t].Min=d[t].h+d[t].g; return; } pushDown(t); if(pos<=d[t].mid) setH(t*2,pos,h); else setH(t*2+1,pos,h); pushUp(t); } void setG(int t,int L,int R,int g) { if(d[t].L==L&&d[t].R==R) { d[t].set(g); return; } pushDown(t); if(R<=d[t].mid) setG(t*2,L,R,g); else if(L>d[t].mid) setG(t*2+1,L,R,g); else setG(t*2,L,d[t].mid,g),setG(t*2+1,d[t].mid+1,R,g); pushUp(t); } i64 query(int t,int L,int R) { if(d[t].L==L&&d[t].R==R) return d[t].Min; pushDown(t); i64 x,y,ans; if(R<=d[t].mid) ans=query(t*2,L,R); else if(L>d[t].mid) ans=query(t*2+1,L,R); else { x=query(t*2,L,d[t].mid); y=query(t*2+1,d[t].mid+1,R); ans=min(x,y); } pushUp(t); return ans; } i64 cal() { build(1,1,n); setG(1,1,1,a[1].h); setH(1,1,0); int i; i64 temp; for(i=2;i<=n;i++) { temp=query(1,a[i-1].x,i-1); setH(1,i,temp); setG(1,a[i].y,i,a[i].h); } return query(1,a[n].x,n); } int main() { int num=0; rush() { RD(n); int i; FOR1(i,n) a[i].get(); init(); printf("Case %d: ",++num); PR(cal()); } }