数列分块入门1-9

数列分块入门1

Description
给出一个长为n 的数列,以及 n 个操作,操作涉及区间加法,单点查值。
Input
第一行输入一个数字n。
第二行输入n个数字,第i个数字为ai,以空格隔开。
接下来输入n行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问ar 的值(l和c忽略)
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
4
1 2 2 3
0 1 3 1
1 0 1 0
0 1 2 2
1 0 2 0
Output
2
5
Hint
n≤50000,−2e31≤others,ans≤2e31−1

思路:
将该数列进行分块,每块长为m则复杂度为O(n/m)+O(m),由基本不等式值m=sqrt(n)时复杂度最低,所以每块长为sqrt(n);具体见代码说明

	/*
	变量介绍:
	a[N]数组存输入的数列
	k为块的个数
	len为块的长度
	L[]数组记录每个块的左下标,R[]数组记录每个块的右下标
	F[]记录每个元素应该属于哪个块
	add[]是加法标记(其数组大小=k)记录每个块加多少,此处为关键!!! 
	*/
	#include<bits/stdc++.h>
	using namespace std;
	typedef long long ll;
	const ll N=1e5+8;
	int  n,i,k,j,op,l,r,c,len;
	ll a[N],L[N],R[N],F[N],add[N];
	
	ll ask(ll x){//单点查询 
		return a[x]+add[F[x]];
	}
	
	void build_block(){
		
		for(i=1;i<=n;i++) cin>>a[i];
		
		len=sqrt(n);
		k=n/len;
		if(n%k) k++;//因为n不一定整除k,eg:n=17,则len=4,此时不够平分,所以加一个块(不完整块),k++; 
		
		for(i=1;i<=k;i++) { //记录每个块的左右下标 
			R[i]=i*len;
			L[i]=R[i-1]+1;
		}
		R[k]=n;//因为n不一定整除k
		 
		 for(i=1;i<=k;i++){
		 	for(j=L[i];j<=R[i];j++){
		 		F[j]=i;  //记录每个元素属于那个块 
			 }
		 }
		
		
	}
	
	void Add(int  l,int  r,int  c){//区间加法 
		if(F[l]==F[r]){   //如果被加区间被包含于一个整块 
			for(i=l;i<=r;i++) a[i]+=c;
		}
		else { //如果被加区间包含多个块(>=2) 
			for(i=l;i<=R[F[l]];i++) a[i]+=c;//用暴力吧前头区间不完整的元素直接改变 
			for(i=L[F[r]];i<=r;i++) a[i]+=c;//用暴力吧后头区间不完整的元素直接改变 
			for(i=F[l]+1;i<F[r];i++) add[i]+=c; //优雅的暴力:最后改变完整区间的add[]标记 
			
		}
		
	}
	
	int main(){
		
		cin>>n;
		
		build_block();//构建块 
		for(int kk=1;kk<=n;kk++){
			cin>>op>>l>>r>>c;
			if(op==0){//修改 
				Add(l,r,c);
			}
			else {//查询 
			printf("%lld\n",ask(r));	
			}
		} 
		return 0;
	} 

数列分块入门2

Description
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的元素个数。
Input
第一行输入一个数字 n。
第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问中[l,r]小于c2的数字的个数。
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input 复制
4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2
Output
3
0
2
Hint
n≤50000, −231≤others,ans≤231−1

思路
分块后每个块分别存入一个容器里,容器对每个块进行排序,维护一个vector,对于区间修改:完整的区间直接对其加法标记修改;不完整的用边角暴力并且暴力后还要对区间重新进行排序,以便下次查询的时候直接用lower_bound来二分查找;对于区间查询,边角暴力解决不完整区间,完整的区间用lower_bound函数来查找。具体见代码!!!!

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n,k,len;
int block,sum;//block为块的长度,sum为块的个数
int a[N];//存放数列元素
int pos[N],add[N];//pos记录第i个元素在第几个块中,tag为操作标记
int ans[N];//维护整块和
vector<int> v[N];
void init(){
    block=sqrt(n);//块的长度
    sum=n/block;//块个数
    if(n%block)
        sum++;
    for(int i=1;i<=n;i++){
        pos[i]=(i-1)/block+1;//第i个元素在第几块中
        v[pos[i]].push_back(a[i]);//保存每个数分块的序号
    }
 
    for(int i=1;i<=sum;i++)//对整块进行排序
        sort(v[i].begin(),v[i].end());
}
void resort(int x){
    v[pos[x]].clear();
    for(int i=(pos[x]-1)*block+1;i<=min(pos[x]*block,n);i++)
        v[pos[x]].push_back(a[i]);
    sort(v[pos[x]].begin(),v[pos[x]].end());
}


void update(int L,int R,int x){
    for(int i=L;i<=min(pos[L]*block,R);i++)//左边的边角料
        a[i]+=x;
    resort(L);//对不完整块排序
    if(pos[L]!=pos[R]){//存在右区间才遍历,防止重复计算
        for(int i=(pos[R]-1)*block+1;i<=R;i++)//右边的边角料
            a[i]+=x;
        resort(R);//对不完整块排序
    }
    for(int i=pos[L]+1;i<=pos[R]-1;i++)//中间的整块
        add[i]+=x;
}

int query(int L,int R,int x){
	
    int res=0;
 
    for(int i=L;i<=min(pos[L]*block,R);i++)//左边的边角
        if(a[i]+add[pos[i]]<x)
            res++;
 
    if(pos[L]!=pos[R])//存在右区间才遍历,防止重复计算
        for(int i=(pos[R]-1)*block+1;i<=R;i++)//右边的边角料
            if(a[i]+add[pos[i]]<x)
                res++;
 
    for(int i=pos[L]+1;i<=pos[R]-1;i++){//中间的整块进行二分查找
        int temp=x-add[i];	
        res+=lower_bound(v[i].begin(),v[i].end(),temp)-v[i].begin();
    }
    return res;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)	 cin>>a[i];
       
    init();
 
    for(int i=1;i<=n;i++){
        int op;
        int l,r,x;
        cin>>op>>l>>r>>x;
        if(!op)    update(l,r,x);
        else        printf("%d\n",query(l,r,x*x));
    }
    return 0;
}

数列分块入门3

Description
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)。
Input
第一行输入一个数字 n 。
第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问[l,r]中c的前驱的值(不存在则输出-1)。
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
Output
3
-1
Hint
n≤100000, −231≤others,ans≤231−1

思路:
大体和题2一样;就是查询的时候需要改一下,每次重置anss=-1,每个区间二分查找,边角就暴力。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n,k,len;
int block,sum,anss;//block为块的长度,sum为块的个数
int a[N];//存放数列元素
int pos[N],add[N];//pos记录第i个元素在第几个块中,tag为操作标记
int ans[N];//维护整块和
vector<int> v[N];
void init(){
    block=sqrt(n);//块的长度
    sum=n/block;//块个数
    if(n%block)
        sum++;
    for(int i=1;i<=n;i++){
        pos[i]=(i-1)/block+1;//第i个元素在第几块中
        v[pos[i]].push_back(a[i]);//保存每个数分块的序号
    }
 
    for(int i=1;i<=sum;i++)//对整块进行排序
        sort(v[i].begin(),v[i].end());
}
void resort(int x){
    v[pos[x]].clear();
    for(int i=(pos[x]-1)*block+1;i<=min(pos[x]*block,n);i++)
        v[pos[x]].push_back(a[i]);
    sort(v[pos[x]].begin(),v[pos[x]].end());
}


void update(int L,int R,int x){
    for(int i=L;i<=min(pos[L]*block,R);i++)//左边的边角料
        a[i]+=x;
    resort(L);//对不完整块排序
    if(pos[L]!=pos[R]){//存在右区间才遍历,防止重复计算
        for(int i=(pos[R]-1)*block+1;i<=R;i++)//右边的边角料
            a[i]+=x;
        resort(R);//对不完整块排序
    }
    for(int i=pos[L]+1;i<=pos[R]-1;i++)//中间的整块
        add[i]+=x;
}

int query(int L,int R,int x){
	anss=-1;
    int res=0;
 
    for(int i=L;i<=min(pos[L]*block,R);i++)//左边的边角
        if(a[i]+add[pos[i]]<x)
            anss=max(anss,a[i]+add[pos[i]]);
 
    if(pos[L]!=pos[R])//存在右区间才遍历,防止重复计算
        for(int i=(pos[R]-1)*block+1;i<=R;i++)//右边的边角料
            if(a[i]+add[pos[i]]<x)
                anss=max(anss,a[i]+add[pos[i]]);
 
    for(int i=pos[L]+1;i<=pos[R]-1;i++){//中间的整块进行二分查找
        int temp=x-add[i];	
        res=lower_bound(v[i].begin(),v[i].end(),temp)-v[i].begin();
        if(res==0) continue;
        if(v[i][res-1]==x) res--;
        anss=max(anss,add[i]+v[i][res-1]);
        
    }
    return anss;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)	 cin>>a[i];
       
    init();
 
    for(int i=1;i<=n;i++){
        int op;
        int l,r,x;
        cin>>op>>l>>r>>x;
        if(!op)    update(l,r,x);
        else        printf("%d\n",query(l,r,x));
    }
    return 0;
}

数列分块入门4

Description
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,区间求和。
Input
第一行输入一个数字 n 。
第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问[l,r]中的所有数字的和 mod (c+1)
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
Output
1
4
Hint
n≤50000, −231≤others,ans≤231−1

思路:
这一题难度降低了,属于第一题的变式,将每个块的和放入ans[]数组里维护;修改完整的区间只需要对加法标记修改,不完整的则直接修改原数组还要修改ans[]数组

上代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
typedef long long ll;
ll n,k,len;
ll block,sum,anss;//block为块的长度,sum为块的个数
ll a[N];//存放数列元素
ll pos[N],add[N];//pos记录第i个元素在第几个块中,tag为操作标记
ll ans[N];//维护整块和
void init(){
    block=sqrt(n);//块的长度
    sum=n/block;//块个数
    if(n%block)
        sum++;
    for(ll i=1;i<=n;i++){
        pos[i]=(i-1)/block+1;//第i个元素在第几块中 
        ans[pos[i]]+=a[i];
    }
 
}

void update(ll L,ll R,ll x){
    for(ll i=L;i<=min(pos[L]*block,R);i++)//左边的边角料
        {
        	a[i]+=x;
        	ans[pos[i]]+=x;
		}
   
    if(pos[L]!=pos[R]){//存在右区间才遍历,防止重复计算
        for(ll i=(pos[R]-1)*block+1;i<=R;i++)//右边的边角料
          {
          	
        	a[i]+=x;
        	ans[pos[i]]+=x;
		
		  }
        
    }
    for(ll i=pos[L]+1;i<=pos[R]-1;i++)//中间的整块
        add[i]+=x;
}


ll query(ll L,ll R,ll x){
	anss=0; 
    
 
    for(ll i=L;i<=min(pos[L]*block,R);i++)//左边的边角
        anss=(anss+a[i]+add[pos[i]])%(x+1);
        
    if(pos[L]!=pos[R])
	 {
	  	 for(ll i=(pos[R]-1)*block+1;i<=R;i++)//右边的边角料
         anss=(anss+a[i]+add[pos[i]])%(x+1);
} 
 
    for(ll i=pos[L]+1;i<=pos[R]-1;i++){//中间的整块进行二分查找
       anss=(anss+ans[i]+add[i]*block)%(x+1);
    }
    return anss%(x+1);
}
int main(){
    cin>>n;
    for(ll i=1;i<=n;i++)	 cin>>a[i];
    memset(ans,0,sizeof(ans));
    init();
 
    for(ll i=1;i<=n;i++){
        ll op;
        ll l,r,x;
        cin>>op>>l>>r>>x;
        if(!op)    update(l,r,x);
        else        printf("%lld\n",query(l,r,x));
    }
    return 0;
}

数列分块入门5

Description
给出一个长为 n 的数列 a1,a2,…,an,以及 n 个操作,操作涉及区间开方,区间求和。
Input
第一行输入一个数字 n 。
第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都开平方(下取整)。
若opt=1,表示询问[l,r]中所有数字的和。
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
Output
6
2

思路:
一个数经几次开方就会变成1或者0,所以标记一下这个块是不是全为1或者0;如果是,则修改就可以忽略,这样就大大减低了复杂度。

/**
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 100007;
int l[N],r[N],block,num,belong[N],n,x,y;
bool flag[N];
ll a[N],lazy[N],b[N];//b[]放每块和 
 void build()
{
    block=sqrt(n);
    num=n/block;if (n%num) num++;
    for (int i=1;i<=num;i++)
        l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;
 
    for (int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
 
    for (int i=1;i<=num;i++)
        for (int j=l[i];j<=r[i];j++)
            b[i]+=a[j];
    
}
 void update(int x,int y,int c)
{
    if (belong[x]==belong[y])//如果在一个快块 
    {
        if (!flag[belong[x]]) 
            for (int i=x;i<=y;i++) 
                b[belong[x]]-=a[i],a[i]=sqrt(a[i]),b[belong[x]]+=a[i];//减去再加 
        return;
    }
    if (!flag[belong[x]])
        for (int i=x;i<=r[belong[x]];i++)
            b[belong[x]]-=a[i],a[i]=sqrt(a[i]),b[belong[x]]+=a[i];
 
    for (int i=belong[x]+1;i<belong[y];i++)
    {
        if (flag[i]) continue;
        flag[i]=1;
        for (int j=l[i];j<=r[i];j++)
        {
            b[i]-=a[j];
            a[j]=sqrt(a[j]);
            b[i]+=a[j];
            if (a[j]!=1) flag[i]=0;
            
        }
    }
    
    if (!flag[belong[y]])
        for (int i=l[belong[y]];i<=y;i++)
            b[belong[y]]-=a[i],a[i]=sqrt(a[i]),b[belong[y]]+=a[i];
    
}
 ll ask(int x,int y,ll c)
{
    int cnt=0;
    ll ans=0;
    if (belong[x]==belong[y])
    {
        for (int i=x;i<=y;i++)
            ans+=a[i];
        return ans;
    }
    for (int i=x;i<=r[belong[x]];i++) 
        ans+=a[i];
    for (int i=l[belong[y]];i<=y;i++)
        ans+=a[i];
    
    for (int i=belong[x]+1;i<belong[y];i++)
    {
        if (flag[i]) ans+=block;
        else ans+=b[i];
    }
   
    return ans;
}
int  main() 
{
    
    int m;
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    build();
    for (int k=1;k<=n;k++)
    {
        int l,r,op;
        ll c;
        cin>>op>>l>>r>>c;
        if (op==0)
            update(l,r,c);
        else 
            cout<<ask(l,r,c)<<endl;
    }
    return 0;
}

数列分块入门6

Description
出一个长为 n 的数列,以及 n 个操作,操作涉及单点插入,单点询问,数据随机生成。
Input
第一行输入一个数字 n 。
第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示在第 l 个数字前插入数字r(c忽略)。
若opt=1,表示询问 ar 的值(l和c忽略)。
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
Output
2
3
Hint
n≤100000, −231≤others,ans≤231−1

思路:
单点插入,之后再重构,以为数据随机,万一所数据只插在一个块里就爆了,所以有必要重构一下块。

#include<bits/stdc++.h>
using namespace std;
const int nmax = 2e6+100;
const int INF = 0x3f3f3f3f;
const int mm = sqrt(2e6+100) + 10;
int n,m;
vector<int> v[mm];
int belong[nmax],num,block;
int a[nmax<<1];
void init(){
    block = 2*sqrt(n);
    num = n / block; if(num % block) num++;
}
void rebuild(){
    int cur = 1;
    for(int i = 1;i<=num;++i) {for(int j = 0;j<v[i].size();++j) {a[cur++] = v[i][j];} v[i].clear();}
    cur --;
    block = 2*sqrt(cur);
    num = cur / block; if(num % block) num++;
    for(int i = 1;i<=cur;++i) {
        belong[i] = (i-1) / block + 1;
        v[belong[i]].push_back(a[i]);
    }
}
int main() {
    cin>>n;
	int temp;
    init();
    for(int i = 1;i<=n;++i){
        scanf("%d",&temp);
        belong[i] = (i-1) / block + 1;
        v[belong[i]].push_back(temp);
    }
    int op,l,r,w;
    for(int j = 1;j<=n;++j){
        scanf("%d%d%d%d",&op,&l,&r,&w);
        if(op == 0){
            int group = 0,pos = 0;
            for(int i = 1;i<=num;++i)
                if(l - (int)v[i].size() >0 ) l -= (int)v[i].size();
                else{ group = i, pos = l; break;}
            v[group].insert(v[group].begin() + pos - 1,r);
            if(v[group].size() > 7 * block) rebuild();
        }
		else{
            int group,pos;
            for(int i = 1;i<=num;++i){
                if(r - (int)v[i].size() > 0) r -= (int)v[i].size();
                else {group = i, pos = r; break;}
            }
            printf("%d\n",v[group][pos-1]);
        }
    }
    return 0;
}

数列分块入门7

Description
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间乘法,区间加法,单点询问。

Input
第一行输入一个数字 n 。
第二行输入 n 个数字,第i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入四个数字opt,l,r,c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示将位于[l,r]之间的数字都乘c。
若opt=2,表示询问ar的值 mod 10007(l和c忽略)
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
7
1 2 2 3 9 3 2
0 1 3 1
2 1 3 1
1 1 4 4
0 1 7 2
1 2 6 4
1 1 6 5
2 2 6 4
Output
3
100
Hint
n≤100000, −231≤others,ans≤231−1

思路:
用2个数组来存放乘标记和加法标记,乘法优先级大于加法

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const int mod = 10007;
int n, block, belog[maxn], opt, l, r, c;
int a[maxn];
int add[maxn];//维护加法标记
int  mul[maxn];//mul维护乘法标记

void query(int r) {
    int t1 = belog[r];
    printf("%d\n", (a[r]*mul[t1] % mod +add[t1] % mod)%mod);
}
void reset(int x) {//每次维护不完整区间都将这个区间的值都更新,再暴力计算
    for(int i = (x - 1) * block + 1; i <= min(x * block, n); i++) {
        a[i] = (a[i] * mul[x] + add[x]) % mod;
    }
    mul[x] = 1;
    add[x] = 0;
}
void updata_add(int l, int r, int c) {//区间加法
    int t1 = belog[l];
    int t2 = belog[r];
    reset(t1);
    for(int i = l; i <= min(r, t1 * block); i++) a[i] += c;
    if(t1 != t2) {
        reset(t2);
        for(int i = (t2 - 1) * block + 1; i <= r; i++) a[i] += c;
        for(int i = t1 + 1; i <= t2 - 1; i++) {
            add[i] += c;
        }
    }
}
void updata_mul(int l, int r, int c) {
    int t1 = belog[l];
    int t2 = belog[r];
    reset(t1);
    for(int i = l; i <= min(r, t1 * block); i++) {
        a[i] = a[i] * c % mod;
    }
    if(t1 != t2) {
        reset(t2);
        for(int i = (t2 - 1) * block + 1; i <= r; i++) {
            a[i] = a[i] * c % mod;
        }
        for(int i = t1 + 1; i <= t2 - 1; i++) {
            add[i] = add[i] * c % mod;
            mul[i] = mul[i] * c % mod;
           
        }
    }
}
int main() {
    cin>>n;
    block = sqrt(n);
    for(int i = 1; i <= n; i++) {
       cin>>a[i];
        belog[i] = (i - 1) / block + 1;
    }
    int num = (n - 1) / block + 1;//几块 
    for(int i = 0; i <= num; i++) {
        mul[i] = 1;
        add[i]=0;
    }
    for(int i=1;i<=n;i++) {
       cin>>opt>>l>>r>>c;
        if(opt == 0) updata_add(l, r, c);
        else if(opt == 1)updata_mul(l, r, c);
        else query(r);
    }
    return 0;
}

数列分块入门8

Description
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c 。
Input
第一行输入一个数字 n 。
第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入三个数字 l,r,c,以空格隔开。
表示先查询[l,r]的数字有多少个是c,再把位于[l,r]的数字都改为c
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
4
1 2 2 4
1 3 1
1 4 4
1 2 2
1 4 2
Output
1
1
0
2
Hint
n≤100000,−231≤others,ans≤231−1

思路:
上开一个数组去维护一下每一块的值是否是一样的,再开一个数组记录如果某一块的值相同那么这个值是多少,比较暴力

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,st,a[N],bl[N],le[N],ri[N];
int b[321],check[321],l,r,c;



void update(int x)
{
	if (!check[x]) return;
	for (int i=le[x];i<=ri[x];i++) a[i]=b[x];
	check[x]=0,b[x]=-1;
}

int q_c(int l,int r,int c)
{
	int ans=0;
	update(bl[l]);
	for (int i=l;i<=min(ri[bl[l]],r);i++)
		if (a[i]==c) ans++; else a[i]=c;
	if (bl[l]!=bl[r])
	{
		update(bl[r]);
		for (int i=le[bl[r]];i<=r;i++)
			if (a[i]==c) ans++; else a[i]=c;
	}
	for (int i=bl[l]+1;i<=bl[r]-1;i++)
		if (check[i])
		{
			if (b[i]==c) ans+=st;
			else b[i]=c;
		}
		else
		{
			for (int j=le[i];j<=ri[i];j++)
				if (a[j]==c) ans++;
			check[i]=1,b[i]=c;
		}
	return ans;
}

int main()
{
	
	cin>>n;
	st=sqrt(n);
	for (int i=1;i<=n;i++) cin>>a[i]; 
	for (int i=1;i<=n;i++)
	{
		bl[i]=(i-1)/st+1;
		if (!le[bl[i]]) le[bl[i]]=i;
		ri[bl[i]]=i;
	}
	for (int i=1;i<=n;i++)
	{
		cin>>l>>r>>c;
		printf("%d\n",q_c(l,r,c));
	}
	return 0;
}

数列分块入门9

Description
给出一个长为 n的数列,以及 n 个操作,操作涉及询问区间的最小众数。
Input
第一行输入一个数字 n 。
第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。
接下来输入 n 行询问,每行输入两个数字l,r,以空格隔开。
表示查询位于[l,r]的数字的众数。
Output
对于每次询问,输出一行一个数字表示答案。
Samples
Input
4
1 2 2 4
1 2
1 4
2 4
3 4
Output
1
2
2
2
Hint
n≤100000,−231≤others,ans≤231−1

思路:
预处理出每块之间的众数和每个数出现的位置,查询时直接块内查询,不完整块直接二分,相减就是这个数出现的次数了,再更新答案即可。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N = 100007;
const int M = 2007;
int maxn[M][M];
int l[N],r[N],block,num,belong[N],n,x,y;
int a[N],lazy[N],b[N],sum[N],mul[N];
vector<int> v[N];
int val[N],tot;
map<int,int> mp;
inline void init(int x)
{
    memset(b,0,sizeof(b));
    int modeNum=0,id=0;
    for (int i=l[x];i<=n;i++)
    {
        b[a[i]]++;
        if (b[a[i]]>modeNum || (b[a[i]]==modeNum && val[a[i]]<val[id] ) )
            modeNum=b[a[i]],id=a[i];
        maxn[x][belong[i]]=id;
    }
}
inline void build()
{
    if (n<=5000) block=sqrt(n);
    else block=80;
    num=n/block;
    if (n%num) num++;
    for (int i=1;i<=num;i++)
        l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;
    for (int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
 
    for (int i=1;i<=num;i++)
        init(i);
    //预处理
}
 
int binary_search(int L,int R,int x)
{
    return upper_bound(v[x].begin(),v[x].end(),R)-lower_bound(v[x].begin(),v[x].end(),L);
    //二分查找
}
bool vis[N];
inline int ask(int L,int R)
{
    memset(vis,0,sizeof(vis));
    int res=maxn[belong[L]+1][belong[R]-1];
    //预处理的好处,能以O(1)的时间复杂度直接的到完整块的最优解
    //如果没有完整块也不要紧,全局变量初始化是0
    int modeNum=binary_search(L,R,res);
    vis[res]=true;
    for (int i=L;i<=r[belong[L]];i++)
        if (!vis[a[i]])
        {
            vis[a[i]]=true;
            int tmp=binary_search(L,R,a[i]);
            if (tmp>modeNum || tmp==modeNum&&val[a[i]]<val[res])
                modeNum=tmp,res=a[i];
            //如果没找过,就去找;如果是更优解,那么就去更新
        }
    if (belong[L]!=belong[R])
    {
        for (int i=l[belong[R]];i<=R;i++)
            if (!vis[a[i]])
            {
                vis[a[i]]=true;
                int tmp=binary_search(L,R,a[i]);
                if (tmp>modeNum || tmp==modeNum&&val[a[i]]<val[res])
                    modeNum=tmp,res=a[i];
            }
    }
    return val[res];
}
int main() 
{
    
    int m;
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
        if (mp[a[i]]==0)
        {
            mp[a[i]]=++tot;
            val[tot]=a[i];
        }
        a[i]=mp[a[i]];
        //离散化
        v[a[i]].push_back(i);
        //将每一处的位置都存下来,到时候好二分查找
    }
    build();
 
    for (int k=1;k<=n;k++)
    {
        int l,r;
        cin>>l>>r;
        cout<<ask(l,r)<<endl;
    }
    return 0;
}

------------------更新ing

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nap!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值