【SP3261】RACETIME - Race Against Time

蒟蒻的第一道分块题,好激动

一、题意

对于一个序列 \(\{a_n\}\),有两种操作。

  1. \(a_i\to x\)
  2. \([l,r]\) 区间中有多少个 \(a_i\le k\)

二、思路

某位学长和我说:

做题目先看数据范围,这很可能提示了做法。

比如这道题。\(n\le10^5\) 同时 \(Q\le 5\times 10^4\)

考虑到区间操作,然后数据范围正好符合 \(n\sqrt n\),那我们就可以快乐的使用分块了!


分块是什么?简而言之,优雅的暴力。

考虑原始暴力是如何处理区间操作的?

例如区间加操作,原始的暴力会从 \(l\) 循环到 \(r\),给修改每个数。而分块,将 \(n\) 个数分割成 \(\sqrt n\) 个块,如果整个块都在要加的区间里,打上标记;剩下的一部分不成整块的,使用原始暴力的方法处理。

显然看出,原始暴力为 \(O(n)\),而分块达到了 \(O(\sqrt n)\)

至于单点修改,直接 \(O(1)\) 处理不就行了。


看本题要求的操作,很容易得到处理方法:

  1. 先把原序列分块。
  2. 对每个块排序
  3. 单点修改时,将原序列的值改掉,再把这个值所在块覆盖上去,并排序。
  4. 区间查询时,由于区间有序,可以使用二分优化。

三、代码

#include <math.h>
#include <vector>
#include <stdio.h>
#include <algorithm>
using namespace std;
char opt;
int n,q,cnt,x,y,len,i,j,k,ans,dep,siz;
int l[10000],r[10000];
vector<int> g[10000];
int own[1000005],s[1000005];
int main(){
    scanf("%d",&n);
    cnt=n/(len=int(sqrt(n)));//小小的压了行,len是块长,cnt是块数。
    for(i=1;i<=n;++i)
        scanf("%d",s+i);//读入原序列
    for(i=1;i<=cnt;++i){//得到每个区间的左端和右端。
    	l[i]=r[i-1]+1;
    	r[i]=n/cnt*i;
	}
    if(r[cnt]!=n){//如果还差一点没有划分,就多加一个块(n/int(sqrt(n))未必整除)。
        r[cnt+1]=n;
        l[cnt+1]=r[cnt]+1;
        ++cnt;
    }
    for(i=1;i<=cnt;++i){
        for(j=l[i];j<=r[i];++j){
            own[j]=i;
            g[i].push_back(s[j]);//收纳入块
        }
        sort(g[i].begin(),g[i].end());//将每个块排序
    }
    scanf("%d",&q);
    while(q--){
        scanf(" %c %d %d",&opt,&x,&y);
        if(opt==0){
            scanf("%d",&k);
            if(own[x]==own[y]){//如果两个在同一个块,直接for循环
                ans=0;
                for(i=x;i<=y;++i)
                    s[i]>=k&&++ans;//由于块长为根号n,此处复杂度不超过根号。
                printf("%d\n",ans);
            }else{
                ans=0;
                for(i=x;i<=r[own[x]];++i)
                    s[i]>=k&&++ans;
                for(i=l[own[y]];i<=y;++i)//两只小尾巴,同样暴力。
                    s[i]>=k&&++ans;
                for(i=own[x]+1;i<own[y];++i)
                    ans+=upper_bound(g[i].begin(),g[i].end(),k)-g[i].begin();//lower_bound找第一个大于等于的元素,并返回迭代器
                printf("%d\n",ans);
                //这一块的复杂度也是根号n
            }
        }else{//单点修改操作
            dep=own[x];
            siz=g[dep].size();
            s[x]=y;
            for(i=0,j=l[dep];i<siz;++i)//推平这个区间
                g[dep][i]=s[j++];//此处复杂度根号n
            sort(g[dep].begin(),g[dep].end());//此处复杂度log 根号n
        }
    }
    return 0;
}

总复杂度最坏时,就是每次都是区间修改,那么为 \(q\sqrt n+\log \sqrt n\),最好就是每次都查询,为 \(q\sqrt n\),显然能过。

再讲一两个小细节。

  1. 读入单独一个字符时,使用 getchar() 可能会被 \r 搞错,所以是有风险的。可以使用读入字符串取第一个字符或是 scanf(" %c",&c),这个空格就是让 scanf 过滤掉空白字符。效率和 getchar() 差不多。
  2. 迭代器减迭代器返回的是整型,表示这个区间的元素个数。
  3. 代码中使用 vector 是因为方便,不代表不能用二维数组写。
posted @ 2023-02-05 17:28  Syara  阅读(11)  评论(0编辑  收藏  举报