• bzoj1012(洛谷P1198)


    Author : hiang

    bzoj1012     洛谷 P1198

    Time Limit: 3 Sec     Memory Limit: 162 MB

    Description

    现在请求你维护一个数列,要求提供以下两种操作:

    1、 查询操作。

    语法:Q L

    功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。

    限制:L不超过当前数列的长度。

    2、 插入操作。

    语法:A n

    功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。

    限制:n是非负整数并且在长整范围内。

    注意:初始时数列是空的,没有一个数。

    Input

      第一行两个整数,M和D,其中M表示操作的个数(M <= 200,000),D如上文中所述,满足D在longint内。接下来M行,查询操作或者插入操作。

    Output

      对于每一个询问操作,输出一行。该行只有一个数,即序列中最后L个数的最大数。

    Sample Input

    5 100
    A 96
    Q 1
    A 97
    Q 1
    Q 2

    Sample Output

    96
    93
    96
     
     
    这里提供四种思路:
    1.线段树     2.ST表     3.单调队列+并查集     4.树状数组
    一、线段树
    这道题虽然可以用线段树的思路来做,但其实连建树都不用,因为一开始数列中并没有数,只需要在数列末尾加数,并查询已有数列即可,所以我只用了一个数组来存储区间最大值。
    注:本题洛谷的数据非常变态,数据加了全是负数和L为0的情况,所以wa了无数发....以下代码是bzoj的AC代码,本代码在洛谷提交的话会全部MLE,需要把char改成字符数组,具体原因不详。
    AC代码:
     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const long long inf=2*1e9;
     4 #define LL long long
     5 LL d,val[800005];
     6 int m,ans;//ans为数组元素个数
     7 char c;
     8 void add(int p,int ll,int rr,int x,LL k)
     9 {//p为下标,ll,rr为p对应区间的左右端点
    10     if(ll==rr)
    11     {
    12         val[p]=k;
    13         return;
    14     }
    15     int mid=(ll+rr)>>1;
    16     if(x<=mid)
    17         add(p<<1,ll,mid,x,k);
    18     if(x>mid)
    19         add(p<<1|1,mid+1,rr,x,k);
    20     val[p]=max(val[p<<1],val[p<<1|1]);
    21 }
    22 LL query(int p,int pl,int pr,int ll,int rr)
    23 {//pl,pr为p对应区间左右端点,ll,rr为要查询的区间左右端点
    24     LL maxx=-inf;
    25     if(pl>=ll&&pr<=rr)
    26         return val[p];
    27     int mid=(pl+pr)>>1;
    28     if(ll<=mid)
    29         maxx=max(maxx,query(p<<1,pl,mid,ll,rr));
    30     if(rr>mid)
    31         maxx=max(maxx,query(p<<1|1,mid+1,pr,ll,rr));
    32     return maxx;
    33 }
    34 int main()
    35 {
    36     LL n,t=0;
    37     scanf("%d%lld",&m,&d);
    38     for(int i=0;i<m;i++)
    39     {
    40         scanf("%*c%c%lld",&c,&n);//bzoj上能过,洛谷要改成字符数组
    41         if(c=='A')
    42         {
    43             n=(n+t)%d;
    44             ans++;
    45             add(1,1,m,ans,n);
    46         }
    47         else
    48         {
    49             t=query(1,1,m,ans-n+1,ans);
    50             printf("%lld
    ",t);
    51         }
    52     }
    53     return 0;
    54 }

    二、ST表

    ST表是一种用于解决RMQ(查询区间最值)的算法,运用了dp思想,查询复杂度仅为O(1),本代码比线段树快了0.8秒左右,代码也更为简洁

    简单介绍一下原理:

    用一个数组st[i][j]来存储区间[i,i+(1<<j)-1]的最值,而该区间最值就等于[i,i+(1<<(j-1))-1]和[1<<(j-1),i+(1<<j)-1]的最大值,存储区间长度为2^j,所以预处理复杂度为O(nlogn)

    查询时只需要将需查询区间[x,y]分成两部分,令k=(int)log2(y-x+1),区间[x,y]的长度就在2^k~2^(k+1)范围内,所以我们就可以将区间分成长度为2^k的两部分,即[x,x+(1<<k)-1],[y-(1<<k)+1,y],两部分可能有重合,只要保证覆盖了区间[x,y]即可

    因为每次修改复杂度与预处理相同,所以不支持在线修改,但因为本题只需要在最后添加元素,所以我们可以反向建ST表(参考了大佬的思路),即st[i,j]存储[i-(1<<j)+1,i]的最值,这样每次修改时前面元素的ST表不受影响,每次修改的复杂度仅为O(logn)

    AC代码:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define LL long long
     4 int m,ans;
     5 char c[2];
     6 LL d,st[200005][21];
     7 void add(int u,LL k)
     8 {
     9     st[u][0]=k;
    10     for(int j=1;u-(1<<j)>=0;j++)
    11         st[u][j]=max(st[u][j-1],st[u-(1<<(j-1))][j-1]);
    12 }
    13 LL ask(int l,int r)
    14 {
    15     int k=(log(r-l+1))/log(2);
    16     return max(st[r][k],st[l+(1<<k)-1][k]);
    17 }
    18 int main()
    19 {
    20     LL n,t=0;
    21     scanf("%d%lld",&m,&d);
    22     for(int i=0;i<m;i++)
    23     {
    24         scanf("%s%lld",c,&n);
    25         if(c[0]=='A')
    26         {
    27             n=(n+t)%d;
    28             ans++;
    29             add(ans,n);
    30         }
    31         else
    32         {
    33             t=ask(ans-n+1,ans);
    34             printf("%lld
    ",t);
    35         }
    36     }
    37     return 0;
    38 }

    三、单调队列+并查集

    本题只要求在数列末尾加数,所以很容易联想到单调队列,本代码比ST表又快了0.3秒左右

    具体思路:

    令队列中小于等于新元素的数全部出队,再另新元素入队,这样可以得到一个递减队列,队列中的每个数就代表数列中从该元素开始到末尾的最大值

    假如要查询第x个元素到末尾的最大值,那么我们只需要从第x个元素开始,找第一个在单调队列里出现的数,该数即为该区间最大值

    但如果要从x开始一个个遍历的话复杂度过高,这里就可以用并查集,将第i个元素与它后面第一个比它大的元素放到一个集合里,这样查询x时就可以直接找到后面第一个比它大的元素,并一直往后找,直到找不到更大的数为止

    AC代码:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define MAXN 200005
     4 int m,d,ans,s;//ans为数列元素个数,s为队列元素个数
     5 int a[MAXN];//原始数列
     6 int q[MAXN];//单调队列,存储下标
     7 int pre[MAXN];//并查集,存储数列中i之后第一个比a[i]大的数的下标
     8 char c[2];
     9 int ufind(int x)
    10 {
    11     while(x!=pre[x])
    12         x=pre[x];
    13     return x;
    14 }
    15 int main()
    16 {
    17     int n,t=0;
    18     scanf("%d%d",&m,&d);
    19     for(int i=0;i<m;i++)
    20     {
    21         scanf("%s%d",c,&n);
    22         if(c[0]=='A')
    23         {
    24             a[++ans]=(n+t)%d;
    25             pre[ans]=ans;//每个数的上级初始化为自己
    26             while(s>0&&a[q[s]]<a[ans])
    27             {
    28                 pre[q[s]]=ans;
    29 s--; 30 }//将队列中小于新元素的上级改为新元素,并出队 31 q[++s]=ans;//新元素入队 32 } 33 else 34 { 35 t=a[ufind(ans-n+1)]; 36 printf("%d ",t); 37 } 38 } 39 return 0; 40 }

     四、树状数组

    树状数组可用于单点修改,区间查询,本题符合该特点

    需要注意的是,查询区间是从L到末尾,所以要反向建树状数组,即更新时从后往前更新,查询时从前往后查询

    AC代码:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 int m,ans,d;
     4 char c[2];
     5 int s[200005];//树状数组
     6 int lowbit(int x)
     7 {
     8     return x&(-x);
     9 }
    10 void update(int x,int k)
    11 {
    12     for(int i=x;i>0;i-=lowbit(i))
    13         s[i]=max(s[i],k);
    14 }
    15 int query(int x)
    16 {
    17     int maxx=-1e9;
    18     for(int i=x;i<=ans;i+=lowbit(i))
    19         maxx=max(maxx,s[i]);
    20     return maxx;
    21 }
    22 int main()
    23 {
    24     int n,t=0;
    25     scanf("%d%d",&m,&d);
    26     for(int i=0;i<m;i++)
    27     {
    28         scanf("%s%d",c,&n);
    29         if(c[0]=='A')
    30         {
    31             n=(n+t)%d;
    32             ans++;
    33             update(ans,n);
    34         }
    35         else
    36         {
    37             t=query(ans-n+1);
    38             printf("%d
    ",t);
    39         }
    40     }
    41     return 0;
    42 }
  • 相关阅读:
    对javascript匿名函数的理解(透彻版)
    js操作Cookie
    C# WinForm WebBrowser (一) MSDN资料
    asp.net 生成静态HTML方法
    ASP.NET 操作XML
    c# WinForm 登陆 Program.cs 中代码
    Jquery 将后台返回的字符串转换成Json格式的数据
    获取数据库中的所有用户表、取数据表的描述信息包括描述说明
    asp.net 发送QQ邮件
    设置服务器文件的访问权限
  • 原文地址:https://www.cnblogs.com/CSGOBESTGAMEEVER/p/10989177.html
Copyright © 2020-2023  润新知