POJ 3368 RMQ-ST

一直感觉RMQ水,没自己写过,今天写了一道题,算是完全独立写的,这感觉好久没有了...

一直以来,都是为了亚洲赛学算法,出现了几个问题:

1、学的其实只是怎么用算法,对算法的正确性没有好好理解,或者说根本没有真的理解算法并从这个算法在做修改延伸;

2、学的很不系统,没有好好对比整理各种题型,更别说好好总结;

3、貌似整天参考别人代码,很少独立做题;

操,这种急功近利的学习方式终于可以在亚洲赛没机会现场赛的时候结束了,想来也是好事


不废话了,入正题

一、RMQ原理

DP思想:dp(i,j)=min(dp(i,j-1),dp(i+2^(j-1),j-1))   这里dp(i,j)表示以i开头长度为2^(j)的区间内的最小值,同理求最大值。说白了还是二分思想,好广泛而牛逼的二分啊。

     时间复杂度分析:初始化O(nlogn),查询O(1)

其实会了动规,应该自己能发明RMQ,为什么我们没想到呢?自己学算法到底只是学怎么用算法还是在学算法本身并从中有创造?值得反思......

原理不难,但是写模版问题还是比较多的...

1、数组开多大?怎么开?
2、查询怎么做?

看代码吧,我做了注释:

#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 200001;
int a[N], d[20];
int st[N][20];

void ReadIn(const int &n)
{
    int i;
    for( i=0; i < n; ++i ) scanf("%d", &a[i]);
}

inline int max(const int &arg1, const int &arg2)
{
    return arg1 > arg2 ? arg1 : arg2;
}

void InitRMQ(const int &n)
{
    int i, j;

    for( d[0]=1, i=1; i < 21; ++i ) d[i] = 2*d[i-1];
    for( i=0; i < n; ++i ) st[i][0] = a[i];
    int k = int( log(double(n))/log(2.0) ) + 1;/*这里写log(2)的话,在poj会一直CE*/
    for( j=1; j < k; ++j )
        for( i=0; i < n; ++i )
        {
            if( i+d[j-1]-1 < n )
            {
                st[i][j] = max(st[i][j-1],st[i+d[j-1]][j-1]);
                /*st数组的设计还是很不错的,省了很多空间,
                    st[i][j]指的是以a[i]开头的长度为2^j的区间的最小值,
                    这算是离散化的思想吗???以一个点代替一个区间*/
            }
            else break; // st[i][j] = st[i][j-1];
        }
}

void Query(const int &Q)
{
    int i;
    for( i=0; i < Q; ++i )
    {
        int x, y, k; // x, y均为下标:0...n-1
        scanf("%d%d", &x, &y);
        k = int( log(double(y-x+1))/log(2.0) );
        printf("%d\n", max(st[x][k], st[y-d[k]+1][k]));
        /*这里也很巧妙。另外,因为st[y-d[k]+1][k]写成st[y-d[k]][k]WA了无数次*/
    }
}

int main(void)
{
    int n, Q;

    while( scanf("%d%d", &n, &Q) != EOF )
    {
        ReadIn(n); InitRMQ(n); Query(Q);
    }
    return 0;
}

对于POJ3368   http://poj.org/problem?id=3368

样例中 

-1 -1 1 1 1 1 3 10 10 10
我处理为 2,4,1,3;
就是2个-1,4个1,1个3,3个10的意思;
另外开了几个数组:
v[ ]意思是原始的数组,存的-1 -1 1 1 1 1 3 10 10 10

val[ ]就是存的 2,4,1,3;

left[i]=j,意思是元素i在原始 数组的下标为j;right[]同理;

pos[i]=num,值为i的元素在val数组的下标。

这样就可以把v[]华为RMQ的数组了;

查询的时候需要处理,ans=max(左边界的值出现的次数,右边界的值出现的次数,除去左右的区间中的次数的最大值)

左右的区间中的次数的最大值,这个用RMQ去算,左边界的值出现的次数,右边界的值出现的次数,需要每次查询自己处理。

注意边界啊,很能容易错


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 100005;
const int HASH = 100000;

int st[N][20],val[N],left[N*2],right[N*2],v[N],pos[N*2],d[N];

void init(int n)
{
    int k,i,j;

    k=(int)(log(double(n))/log(2.0))+1;
    for(d[0]=1,i=1;i<21;i++)d[i]=2*d[i-1];
    for(i=0;i<n;i++)st[i][0]=val[i];
    for(j=1;j<k;j++)
    {
        for(i=0;i<n;i++)
            if(i+d[j-1]-1<n)
                st[i][j]=max(st[i][j-1],st[i+d[j-1]][j-1]);
            else
                break;
    }

}

void query(int x,int y)
{
    int l,r,a,b,ans;
    a=v[x];
    b=v[y];
    if(pos[b+HASH]>=pos[a+HASH]+2)
    {
        r=right[a+HASH];
        int k=(int)(log((double)(pos[b+HASH]-pos[a+HASH]-1))/log(2.0));
        ans=max(r-x+1,st[pos[a+HASH]+1][k]);
        l=left[b+HASH];

        ans=max(ans,y-l+1);
        ans=max(ans,st[pos[b+HASH]-d[k]][k]);
        printf("%d\n",ans);
    }
    else
        if(pos[b+HASH]==pos[a+HASH]+1)
        {
            r=right[a+HASH];
            l=left[b+HASH];
            printf("%d\n",max(r-x+1,y-l+1));
        }
        else

            {
                printf("%d\n",y-x+1);
            }


}

int main()
{
    //freopen("poj 3368.txt","r",stdin);
    int i,l,r,n,t,b,num,q;

    while(scanf("%d",&n)&&n)
    {
        memset(val,0,sizeof(val));
        memset(st,0,sizeof(st));
        scanf("%d",&q);

        scanf("%d",&b);
        val[num=0]++;
        left[b+HASH]=right[b+HASH]=0;
        v[0]=b;
        pos[b+HASH]=num;
        for(i=1;i<n;i++)
        {
            scanf("%d",&t);
            v[i]=t;
            if(t==b)
            {
                val[num]++;
            }
            else
            {
                right[b+HASH]=i-1;
                pos[b+HASH]=num;
                b=t;
                left[b+HASH]=i;
                val[++num]++;
                pos[b+HASH]=num;
            }
        }

        init(++num);
        for(i=0;i<q;i++)
        {
            scanf("%d%d",&l,&r);
            query(l-1,r-1);
        }
    }

    return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值