题目链接: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());
}
}