线段树 ST算法 RMQ poj 3264 Balanced Lineup 解题报告

线段树 RMQ poj 3264

Balanced Lineup
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 40450 Accepted: 19011
Case Time Limit: 2000MS
Description
For the daily milking, Farmer John’s N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.
Farmer John has made a list of Q (1 ≤ Q ≤ 200,000) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.
Input
Line 1: Two space-separated integers, N and Q.
Lines 2..N+1: Line i+1 contains a single integer that is the height of cow i
Lines N+2..N+Q+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.
Output
Lines 1..Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.
Sample Input
6 3
1
7
3
4
2
5
1 5
4 6
2 2
Sample Output
6
3
0
Source
USACO 2007 January Silver
第一次自己写的线段树。这是最简单的线段树应用:RMQ。注意:因为节点结构体有不仅仅有区间范围,还有区间最大值,最小值。所以建树的时候在末尾要回溯求区间最大最小值。
以下是线段树解法:

#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include<iostream>
#define MAXN 50010
#define Lson(x) ((x) << 1)
#define Rson(x) (((x) << 1) + 1)
using namespace std;
int N,Q,data[MAXN];
struct node
{
    int L,R,MaxDif,MinDif;
}SegTree[MAXN * 4];
void Build(int L,int R,int Node){//口诀:赋值,判断,递归,回溯
    //cout<<L<<" "<<R;
    SegTree[Node].L=L;SegTree[Node].R=R;//赋值
    if(L==R){//判断
        SegTree[Node].MaxDif=SegTree[Node].MinDif=data[L];
        return ;
    }
    int _mid=(SegTree[Node].L+SegTree[Node].R)>>1;//递归
    //cout<<" "<<_mid<<endl;
    Build(L,_mid,Lson(Node));
    Build(_mid+1,R,Rson(Node));
    SegTree[Node].MaxDif=max(SegTree[Lson(Node)].MaxDif,SegTree[Rson(Node)].MaxDif); //回溯
    SegTree[Node].MinDif=min(SegTree[Lson(Node)].MinDif,SegTree[Rson(Node)].MinDif);
}
int query(int L,int R,int Node,int flag){
   // cout<<L<<" "<<R<<" "<<Node<<"   "<<" "<<flag<<" "<<SegTree[Node].L<<" "<<SegTree[Node].R<<endl;
    if(L==SegTree[Node].L&&R==SegTree[Node].R) {
        if(flag) return SegTree[Node].MaxDif;
        else return SegTree[Node].MinDif;
    }
    int _mid=(SegTree[Node].L+SegTree[Node].R)>>1;
    if(R<=_mid) return query(L,R,Lson(Node),flag);
    else if(L>=_mid)return query(L,R,Rson(Node),flag);
    else {
 //   cout<<SegTree[Node].L<<" "<<SegTree[Node].R<<" "<<"_mid:"<<_mid<<endl;
     int  a=query(_mid+1,R,Rson(Node),flag);
     int  b=query(L,_mid,Lson(Node),flag);
       if(flag) return max(a,b);
        else return min(a,b);
    }
}
int main()
{
    freopen("output.txt","w",stdout);
    freopen("input.txt","r",stdin);
    while(scanf("%d%d",&N,&Q)!=EOF){
        for(int i=1;i<=N;i++) scanf("%d",&data[i]);
        Build(1,N,1);
     /**<   for(int i=1;i<=4*N;i++){
            cout<<"#Node"<<i<<":"<<SegTree[i].L<<" ";
            cout<<SegTree[i].R<<" "<<SegTree[i].MaxDif<<" ";
            cout<<SegTree[i].MinDif<<endl;
        } */
        while(Q--){
            int x,y;
            scanf("%d%d",&x,&y);
            int a=query(x,y,1,1);
            int b=query(x,y,1,0);
          //  cout<<a<<" "<<b<<endl;
            printf("%d\n",a-b);
        }
    }
    return 0;
}

ST算法:一种在线算法。所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST(Sparse Table 稀疏表)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。
以求区间最大值为例:
1、预处理:
用动态规划(DP)解决,需要知道初值和状态转移方程。定义F(i, j)表示从第i个数起连续2^j个数中,即区间[i,i+2^j-1](2^j<=n,所以j<=log2(n)),的最大值。例如数列3 2 4 5 6 8 1 2 9 7,F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。F[1,2]=5,F[1,3]=8,F[2,0]=2,F[2,1]=4……从这里可以看出F[i,0]其实就等于A[i]。DP的初值已经有了,剩下的就是状态转移方程。我们把F[i,j]平均分成两段(因为f[i,j]一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。F[i,j]就是这两段的最大值中的最大值。于是我们得到了动态规划方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])。
2、查询:
假设要查询从i到j这一段的最大值, 那么我们先求出一个最大的k, 使得k满足2^k <= (j- i + 1)。于是我们就可以把[i, j]分成两个(部分重叠的)长度为2^k的区间: [i, i+2^k-1], [j-2^k+1,j];而我们之前已经求出了f(i, k)为[i, i+2^k-1]的最大值, f(j-2^k+1, k)为[j-2^k+1, j]的最大值(根据f(i,j)的定义)。我们只要返回其中更大的那个, 就是我们想要的答案, 这个算法的时间复杂度是O(1)的。

#include <cstdio>
#include <cmath>
#include <cstring>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int N = 50005;
int data[N],n;
int dpmin[N][20],dpmax[N][20];
void Deal ()
{
    int i,j;
    for (i=1;i<=n;i++)//初值
    {
        dpmin[i][0]=data[i];
        dpmax[i][0]=data[i];
    }
    //状态转移,决定了复杂度为nlogn
    for (j=1; j<=log((double)(n+1))/log(2.0) ;j++)
        for (i=1; i+(1<<j)-1<=n ;i++)
        {
            dpmin[i][j]=min( dpmin[i][j-1],dpmin[i+(1<<(j-1))][j-1] );
            dpmax[i][j]=max( dpmax[i][j-1],dpmax[i+(1<<(j-1))][j-1] );
        }
}

int Get (int a,int b)
{
    int k = log(1.0*(b-a+1))/log(2.0);
    int up=max( dpmax[a][k] , dpmax[b-(1<<k)+1][k] );
    int down=min( dpmin[a][k] , dpmin[b-(1<<k)+1][k] );
    return up-down;
}
int main ()
{
    int a,b,m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)  scanf("%d",&data[i]);
    memset(dpmin,0,sizeof(dpmin));
    memset(dpmax,0,sizeof(dpmax));
    Deal ();
    while (m--)
    {
        scanf("%d%d",&a,&b);
        printf("%d\n",Get(a,b));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值