[SCOI2007]降雨量(st表)

题目描述

我们常常会说这样的话:“X年是自Y年以来降雨量最多的”。它的含义是X年的降雨量不超过Y年,且对于任意Y < < <script type="math/tex" id="MathJax-Element-11"><</script>Z < < <script type="math/tex" id="MathJax-Element-12"><</script>X,Z年的降雨量严格小于X年。例如2002,2003,2004和2005年的降雨量分别为4920,5901,2832和3890,则可以说“2005年是自2003年以来最多的”,但不能说“2005年是自2002年以来最多的”由于有些年份的降雨量未知,有的说法是可能正确也可以不正确的。

输入格式:

输入仅一行包含一个正整数 n n ,为已知的数据。以下n行每行两个整数 yi y i ri r i ,为年份和降雨量,按照年份从小到大排列,即 yi<yi+1 y i < y i + 1 。下一行包含一个正整数 m m ,为询问的次数。以下m行每行包含两个数Y和X,即询问“X年是自Y年以来降雨量最多的。”这句话是必真、必假还是“有可能”。

输出格式:

对于每一个询问,输出true,false或者maybe。

输入样例:
6
2002 4920
2003 5901
2004 2832
2005 3890
2007 5609
2008 3024
5
2002 2005
2003 2005
2002 2007
2003 2007
2005 2008
输出样例:
false
true
false
maybe
false

100%的数据满足: 1<=n<=50000,1<=m<=10000,109<=yi<=109,1<=ri<=109 1 <= n <= 50000 , 1 <= m <= 10000 , − 10 9 <= y i <= 10 9 , 1 <= r i <= 10 9

传送门!!!

要是你第一眼就觉得是一道线段树的题,那怕是没救了(虽然也可以做),显然我们发现这道题没有修改操作,我们就会想到st表或者前缀和。这道题是求区间最大值,所以果断使用st表。然而瞎操作一波发现并没有那么简单。这道题难的地方和数据结构没啥关系,主要是判断十分恶心。各种条件判断不写对拍根本想不到啊。而且这组样例水得不能再水了。后面询问的年份可以不在前面出现,看看这组样例:1 2000 2000 1 1999 2001,应该输出maybe,然而我第一次re了(偷笑)。所以情况特别多啊,这个等会儿在程序里讲。还有一个点就是离散化,这个很简单数据保证有序,连序都不用排了。然后亲测一个隐藏条件:保证输入询问 y1<y2 y 1 < y 2

代码如下(第一次st表就讲细一点):

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<cmath>  
using namespace std;  
struct lxy{  
    int rain,year;  
}data[100005];//存数据  

int n,m;  
int st1[100005][20];//判断区间最大值的下表  
//记得st表数组开双倍  
bool st2[100005][20];//判断区间是否为连续年份,满足为0,不满足为1  
int lg[100005];//存每个数的log,向下取整  

int er(int l,int r,int q)//查找是否存在年份为q,存在返回下表,否则返回0  
{  
    int mid=(l+r)/2;  
    if(l==r)  
      return l;  
    if(data[mid].year<=q)  
      return er(mid+1,r,q);  
    if(data[mid].year>q)  
      return er(l,mid,q);  
}  

int find(int l,int r,int q)//查找年份大于q的最小值下标  
{  
    if(l>r)  
      return 0;  
    int mid=(l+r)/2;  
    if(q==data[mid].year)  
      return mid;  
    if(data[mid].year<q)  
      return find(mid+1,r,q);  
    else  
      return find(l,mid-1,q);  
}  

int ques(int x1,int x2)//查询函数,false返回1,true返回2,maybe返回3  
{  
    int y1=find(1,n,x1),y2=find(1,n,x2);  
    if(y1==0&&y2==0)//如果两个询问年份都不知道,输出maybe  
      return 3;  
    int z1=er(1,n,x1),z2=er(1,n,x2);  
    while(data[z2].year!=x2&&data[z2].year>=x2)//z2是不超过x2的最大年份下标  
    if(z1>z2)//如果满足,一定是maybe,可以自行脑补  
      return 3;  
    int p=lg[z2-z1+1];  
    int w;  
    if(data[st1[z1][p]].rain<data[st1[z2-(1<<p)+1][p]].rain)//查找区间最大值的下标  
      w=st1[z2-(1<<p)+1][p];  
    else  
      w=st1[z1][p];  
    if(y2!=0&&w!=z2)//如果x2知道且最大值下标不为x2,输出false  
      return 1;  
    if(y1!=0)//如果x1知道且x1降雨量比区间最大值小,输出false  
      if(data[y1].rain<=data[w].rain)  
        return 1;  
    if(y1==0||y2==0||st2[z1][p]==1||st2[z2-(1<<p)+1][p]==1)//如果区间是断的,输出maybe  
      return 3;  
    if(y1!=0)//如果x1存在,还要判断x1与区间是否连接  
      if(data[z1-1].year+1!=data[z1].year)  
        return 3;  
    return 2;//最后输出true  
}  

int main()  
{  
    scanf("%d",&n);  
    int p=2;  
    int t=0;  
    for(int i=1;i<=n;i++)//算log,这样可以O(1)查询咯(虽然查询中的二分也让复杂度变成logn了)  
    {  
        if(i<p)  
          lg[i]=t;  
        else  
        {  
          p=p<<1;  
          t++;  
          lg[i]=t;  
        }  
    }  
    for(int i=1;i<=n;i++)  
    {  
        scanf("%d%d",&data[i].year,&data[i].rain);  
        st1[i][0]=i;  
    }  
      for(int j=1;j<=15;j++)//st表初始化,j大小数据范围:2^j<数据个数,不然容易超界  
        for(int i=1;i<=n;i++)  
      {  
        if(data[st1[i][j-1]].rain<data[st1[i+(1<<(j-1))][j-1]].rain)  
          st1[i][j]=st1[i+(1<<(j-1))][j-1];  
        else  
          st1[i][j]=st1[i][j-1];  
        if(data[i+(1<<(j-1))-1].year+1!=data[i+(1<<(j-1))].year)  
          st2[i][j]=1;  
        else  
          st2[i][j]=st2[i][j-1]|st2[i+(1<<(j-1))][j-1];  
      }  
    scanf("%d",&m);  
    for(int i=1;i<=m;i++)//查询询问  
    {  
        int x,y;  
        scanf("%d%d",&x,&y);  
        int ans;  
        ans=ques(x,y);  
        if(ans==1)  
          printf("false\n");  
        if(ans==2)  
          printf("true\n");  
        if(ans==3)  
          printf("maybe\n");  
    }  
}  

这样就完成啦,复杂度是 O(nlogn) O ( n l o g n )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值