《阿里巴巴集团杯》2011(春)HIT ACM程序设计竞赛 解题报告

传送门

A题 Best Fit Ring

题意:修改某点值,求最大区间和
当时一直想用树状数组做,结果还是没做出来。正解应该是线段树来做,mark下,以后做。
//待做

B题Seven Roads

待做
//待做

C题Shortest Path

题意:

    数轴上有n个点,从0..n-1,每个点处有个x[i],表示在此点可以一步到达[i,i+x[i]]中任意一整点处,问从0开始,最少几步可以到达n-1

此题明显是动归,但有不同的优化做法:直接动归,单调队列,分段法。。。

直接动归:

    f[i]表示到达i时,所走的最小步数,然后更新f[k],f[k]=min(f[k],f[i]+1]) i < k <= i+x[i];但是这个时间复杂度太高,很可能超时

单调队列优化:

    考虑最终的f,满足单调性,即对于x1<x2 ,有f[x1]<=f[x2]
    故保存一个队列,队列里每个元素有两个值,一是所能到达的最远位置,二是所走的步数
    此队列的单调性为:最远位置有小到大,所走步数有小到大
    这样,对于每个i,先判断队首位置是否能达到i,不行的话队首指针后移,直到能打到i,这时f[i]就是队首元素的步数+1,然后把i从队尾入队,若i+x[i]不比队尾的最远位置大,i不用入队,否则判断f[i]与队尾的步数大小比较,若不大于,则尾指针前移,直到不满足为止,然后把i加入到尾部
    这样,n个数,每个数入队一次,出队一次,平摊复杂度为O(1),总复杂度为O(n)

分段法:
    考虑上面的单调队列,任一时刻,队列里最多只会有2个元素,这时由于每个点往后加的步数都是1所决定的,即步数都为k的那一段只由步数都为k-1的那一段所决定,跟步数为0..k-2的点没关系。
    于是可以按步数动归,即由当前步数为k这一段,求出步数为k+1这一段的范围
    设步数为k这一段区间范围为[p,q],则k+1这一段的范围为[q+1,max(i+x[i])],其中p< =i<=q
    这样复杂度也是O(n)
    明显这种做法比上一种好写,一般越好写代表问题越特殊,如果题目再一般化一点,这种做法就行不通了。

一般化的问题:

    数轴上有n个点,从0..n-1,每个点处有个x[i],step[i]表示在此点可以step[i]步到达[i,i+x[i]]中任意一整点处,问从0开始,最少几步可以到达n-1

这样第二种做法就很容易解决了。单调队列做法:

#include <iostream>
#define N 100010
using namespace std;
int a[N],q[N],p[N],f[N];
int main()
{
    int t,i,head,tail,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(i=0;i<n ;i++)
            scanf("%d",a+i);
        head=0;
        tail=1;
        q[0]=0;
        p[0]=a[0];
        for(i=1;i<n;i++)
        {
            while(head<tail&&p[head]<i)head++;
            if(head==tail)break;
            f[i]=q[head]+1;
            while(head<tail&&i+a[i]>=p[tail-1]&&q[head]+1< =q[tail-1])tail--;
            if(head<tail&&i+a[i]<=p[tail-1]&&q[head]+1>=q[tail-1])continue;
            //cout< <p[head]<<' '<<q[head]<<endl;
            q[tail]=q[head]+1;
            p[tail]=i+a[i];
            tail++;
        }
        if(i==n)printf("%d\n",f[n-1]);
        else puts("-1");
    }
    return 0;
}


你可能感兴趣的:(《阿里巴巴集团杯》2011(春)HIT ACM程序设计竞赛 解题报告)