数列分块入门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