分块,莫队
//区间加法单点查询
/*
我们给每个块设置一个加法标记(就是记录这个块中元素一起加了多少),每次操作对每个整块
直接O(1)标记,而不完整的块由于元素比较少,暴力修改元素的值。
每次询问时返回元素的值加上其所在块的加法标记。
*/
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int a[N];
int vis[N];
int bl[N];//分块
int blo;//分块大小
int sum[N];//加法标记
void update(int L,int R,int x)
{
if(bl[L]==bl[R])
{
for(int i=L;i<=R;i++)
a[i]+=x;
}
else
{
for(int i=L;i<=bl[L]*blo;i++)
a[i]+=x;
for(int i=bl[L]+1;i<=bl[R]-1;i++)
sum[i]+=x;
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
a[i]+=x;
}
}
int query(int x)
{
return a[x]+sum[bl[x]];
}
int main()
{
ios::sync_with_stdio(false);
int n,ty,l,r,c;
cin>>n;
blo=sqrt(n);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
bl[i]=(i-1)/blo+1;
for(int i=0;i<n;i++)
{
cin>>ty>>l>>r>>c;
if(ty==0)
{
update(l,r,c);
}
else
{
cout<<a[r]+sum[bl[r]]<<endl;
}
}
//cout << "Hello world!" << endl;
return 0;
}
//区间加法,询问区间内小于某个值x的元素个数。
/*
我们先来思考只有询问操作的情况,不完整的块枚举统计即可;而要在每个整块内寻找小于一个值的
元素数,于是我们不得不要求块内元素是有序的,这样就能使用二分法对块内查询,需要预处理时每块做一遍排序,复杂度O(nlogn),每次查询在√n个块内二分,以及暴力2√n个元素。
那么区间加怎么办呢?
套用第一题的方法,维护一个加法标记,略有区别的地方在于,不完整的块修改后可能会使得该块内
数字乱序,所以头尾两个不完整块需要重新排序,复杂度分析略。
在加法标记下的询问操作,块外还是暴力,
查询小于(x – 加法标记)的元素个数,块内用(x – 加法标记)作为二分的值即可。
*/
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e6+5;
vector<int>vis[600];//比较动态数组
int a[N];
int bl[N];//分块
int blo;//分块大小
int sum[N];//加法标记
int n;
void update(int x)//不完全区间每次修改值,原有顺序被改变,需要重新排序
{
vis[x].clear();
for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
vis[x].push_back(a[i]);
sort(vis[x].begin(),vis[x].end());
}
void add(int L,int R,int x)
{
if(bl[L]==bl[R])
{
for(int i=L;i<=R;i++)
a[i]+=x;
update(bl[L]);
}
else
{
for(int i=L;i<=min(bl[L]*blo,n);i++)
a[i]+=x;
update(bl[L]);
for(int i=bl[L]+1;i<=bl[R]-1;i++)
sum[i]+=x;
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
a[i]+=x;
update(bl[R]);
}
}
int query(int L,int R, int x)
{
int ans=0;
if(bl[L]==bl[R]){
for(int i=L;i<=R;i++)
if(a[i]+sum[bl[i]]<x)
ans++;
return ans;
}
for(int i=L;i<=bl[L]*blo;i++)
if(a[i]+sum[bl[i]]<x)
ans++;
for(int i=bl[L]+1;i<=bl[R]-1;i++)
{
int tag=x-sum[i];
ans+=lower_bound(vis[i].begin(),vis[i].end(),tag)-vis[i].begin();
}
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
if(a[i]+sum[bl[i]]<x)
ans++;
return ans;
}
int main()
{
ios::sync_with_stdio(false);
int ty,l,r,c;
cin>>n;
blo=sqrt(n);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/blo+1;
vis[bl[i]].push_back(a[i]);//入队
}
for(int i=1;i<bl[n];i++)
sort(vis[i].begin(),vis[i].end());//每块排序
for(int i=0;i<n;i++)
{
cin>>ty>>l>>r>>c;
if(ty==0)
{
add(l,r,c);//修改区间值
}
else
{
cout<<query(l,r,c*c)<<endl;
}
}
//cout << "Hello world!" << endl;
return 0;
}
//操作涉及区间加法,询问区间内小于某个值 的前驱(比其小的最大元素)
/*
不过这题其实想表达:可以在块内维护其它结构使其更具有拓展性,比如放一个 set ,
这样如果还有插入、删除元素的操作,会更加的方便。
区间加法,求某个数的前驱
用set维护方便很多(其实相当于一颗splay把我觉得,手动艾特lynkin同学用的二分查找前驱过了)
依旧是完整的块直接lower_bound
不完整的块还是继续暴力
不过要注意set的使用方法 删掉再进行暴力修改 再加回去
分块的调试检测技巧:
可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。
*/
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
const int N=1e6+5;
set<int>vis[6000];//set相当于一颗树,红黑平衡二叉树
int a[N];
int bl[N];//分块
int blo;//分块大小
int sum[N];//加法标记
int n;
void add(int L,int R,int x)
{
if(bl[L]==bl[R])
{
for(int i=L;i<=R;i++)
{
vis[bl[L]].erase(a[i]);
a[i]+=x;
vis[bl[L]].insert(a[i]);
}
}
else
{
for(int i=L;i<=min(bl[L]*blo,n);i++)
{
vis[bl[L]].erase(a[i]);
a[i]+=x;
vis[bl[L]].insert(a[i]);
}
for(int i=bl[L]+1;i<=bl[R]-1;i++)
sum[i]+=x;
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
{
vis[bl[R]].erase(a[i]);
a[i]+=x;
vis[bl[R]].insert(a[i]);
}
}
}
int query(int L,int R, int x)
{
int ans=-1;
if(bl[L]==bl[R]){
for(int i=L;i<=R;i++)
if(a[i]+sum[bl[i]]<x)
ans=max(ans,a[i]+sum[bl[i]]);
return ans;
}
for(int i=L;i<=min(bl[L]*blo,n);i++)
if(a[i]+sum[bl[i]]<x)
ans=max(ans,a[i]+sum[bl[i]]);
for(int i=bl[L]+1;i<=bl[R]-1;i++)
{
int tag=x-sum[i];
set<int>::iterator k=vis[i].lower_bound(tag);
//定义一个k的指针 一开始指向t的后继
//upper_bound(i) 返回的是键值为i的元素可以插入的最后一个位置(上界)
//lower_bound(i) 返回的是键值为i的元素可以插入的位置的第一个位置(下界)。
if(k==vis[i].begin()) continue;
k--;//所以要减一
ans=max(ans,*k+sum[i]);
}
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
if(a[i]+sum[bl[i]]<x)
ans=max(ans,a[i]+sum[bl[i]]);
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int ty,l,r,c;
cin>>n;
blo=sqrt(n);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/blo+1;
vis[bl[i]].insert(a[i]);//入队
}
for(int i=0;i<n;i++)
{
cin>>ty>>l>>r>>c;
if(ty==0)
{
add(l,r,c);//修改区间值
}
else
{
cout<<query(l,r,c)<<endl;
}
}
//cout << "Hello world!" << endl;
return 0;
}
//区间加法,区间求和。
/*
这题的询问变成了区间上的询问,不完整的块还是暴力;而要想快速统计完整块的答案,需要维护每个块的元素和,先要预处理一下。
考虑区间修改操作,不完整的块直接改,顺便更新块的元素和;完整的块类似之前标记的做法,直接根据块的元素和所加的值计算元素和的增量。
*/
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
int n;
ll a[N];
ll vis[N];
int bl[N];//分块
int blo;//分块大小
ll sum[N];//加法标记
ll add[N];
void init()
{
for(ll i=1;i<=n;i++)
{
add[bl[i]]+=a[i];
}
}
void update(ll L,ll R,ll x)
{
if(bl[L]==bl[R])
{
for(ll i=L;i<=R;i++)
a[i]+=x,add[bl[L]]+=x;
}
else
{
for(ll i=L;i<=bl[L]*blo;i++)
a[i]+=x,add[bl[L]]+=x;
for(ll i=bl[L]+1;i<=bl[R]-1;i++)
sum[i]+=x;
for(ll i=(bl[R]-1)*blo+1;i<=R;i++)
a[i]+=x,add[bl[R]]+=x;
}
}
ll query(ll L,ll R,ll c)
{
ll summ=0;
if(bl[L]==bl[R])
{
for(ll i=L;i<=R;i++)
summ=(summ%c+a[i]%c)%c,summ=(summ%c+sum[bl[L]]%c)%c;
}
else
{
for(ll i=L;i<=bl[L]*blo;i++)
summ=(summ%c+a[i]%c)%c,summ=(summ%c+sum[bl[L]]%c)%c;
for(ll i=bl[L]+1;i<=bl[R]-1;i++)
summ=(summ%c+add[i]%c+sum[i]*blo%c)%c;
//sum[i]*blo最好不要在加法的时候加到add里,由于数字太大可能会爆掉
for(ll i=(bl[R]-1)*blo+1;i<=R;i++)
summ=(summ%c+a[i]%c)%c,summ=(summ%c+sum[bl[R]]%c)%c;
}
return summ;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
ll ty,l,r,c;
cin>>n;
blo=sqrt(n);
for(ll i=1;i<=n;i++)
cin>>a[i];
for(ll i=1;i<=n;i++)
bl[i]=(i-1)/blo+1;
init();
for(ll i=0;i<n;i++)
{
cin>>ty>>l>>r>>c;
if(ty==0)
{
update(l,r,c);
//cout<<add[1]<<" "<<add[2]<<endl;
}
else
{
cout<<query(l,r,c+1)<<endl;
}
}
//cout << "Hello world!" << endl;
return 0;
}
//操作涉及区间开方,区间求和。
/*
给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。
稍作思考可以发现,开方操作比较棘手,主要是对于整块开方时,必须要知道每一个元素,才能知道他们开方后的和,也就是说,难以快速对一个块信息进行更新。
看来我们要另辟蹊径。不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成 0 或者 1。
如果每次区间开方只不涉及完整的块,意味着不超过2√n个元素,直接暴力即可。
如果涉及了一些完整的块,这些块经过几次操作以后就会都变成 0 / 1,于是我们采取一种分块优化的暴力做法,只要每个整块暴力开方后,记录一下元素是否都变成了 0 / 1,区间修改时跳过那些全为 0 / 1 的块即可。
这样每个元素至多被开方不超过4次,显然复杂度没有问题。
*/
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
int n;
ll a[N];
ll vis[N];
int bl[N];//分块
int blo;//分块大小
ll sum[N];//加法标记
ll add[N];
void init()
{
for(ll i=1;i<=n;i++)
{
add[bl[i]]+=a[i];
}
}
void update(ll L,ll R,ll x)
{
if(bl[L]==bl[R])
{
for(ll i=L;i<=R;i++)
add[bl[L]]-=a[i],a[i]=floor(sqrt(a[i])),add[bl[L]]+=a[i];
}
else
{
for(ll i=L;i<=bl[L]*blo;i++)
add[bl[i]]-=a[i],a[i]=floor(sqrt(a[i])),add[bl[i]]+=a[i];
for(ll i=bl[L]+1;i<=bl[R]-1;i++)
{
if(vis[i]==0)
{
vis[i]=1;
for(int j=(i-1)*blo+1;j<=i*blo;j++)
{
add[bl[j]]-=a[j],a[j]=floor(sqrt(a[j])),add[bl[j]]+=a[j];
if(a[j]>1)
vis[i]=0;
}
}
}
for(ll i=(bl[R]-1)*blo+1;i<=R;i++)
add[bl[i]]-=a[i],a[i]=floor(sqrt(a[i])),add[bl[i]]+=a[i];
}
}
ll query(ll L,ll R,ll c)
{
ll summ=0;
if(bl[L]==bl[R])
{
for(ll i=L;i<=R;i++)
summ+=a[i];
}
else
{
for(ll i=L;i<=bl[L]*blo;i++)
summ+=a[i];
for(ll i=bl[L]+1;i<=bl[R]-1;i++)
summ+=add[i];
for(ll i=(bl[R]-1)*blo+1;i<=R;i++)
summ+=a[i];
}
return summ;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
ll ty,l,r,c;
cin>>n;
blo=sqrt(n);
for(ll i=1;i<=n;i++)
cin>>a[i];
for(ll i=1;i<=n;i++)
bl[i]=(i-1)/blo+1;
init();
for(ll i=0;i<n;i++)
{
cin>>ty>>l>>r>>c;
if(ty==0)
{
update(l,r,c);
}
else
{
cout<<query(l,r,c)<<endl;
}
}
//cout << "Hello world!" << endl;
return 0;
}
//操作涉及单点插入,单点询问
/*
给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。
先说随机数据的情况
之前提到过,如果我们块内用数组以外的数据结构,能够支持其它不一样的操作,比如此题每块内可以放一个动态的数组,每次插入时先找到位置所在的块,再暴力插入,把块内的其它元素直接向后移动一位,当然用链表也是可以的。
查询的时候类似,复杂度分析略。
但是这样做有个问题,如果数据不随机怎么办?
如果先在一个块有大量单点插入,这个块的大小会大大超过√n,那块内的暴力就没有复杂度保证了。
还需要引入一个操作:重新分块(重构)
每根号n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。
当然,也可以当某个块过大时重构,或者只把这个块分成两半。
*/
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
int n,mx;
ll a[N];
ll vis[N];
int bl[N];//分块
int blo;//分块大小
ll sum[N];//加法标记
ll add[N];
vector<int>ans[2000];
void init()
{
for(ll i=1;i<=n;i++)
{
add[bl[i]]+=a[i];
}
}
///重构分块
void res()
{
int p=0;
for(int i=1;i<=mx;i++)
{
for(int j=0;j<ans[i].size();j++)
a[++p]=ans[i][j];
ans[i].clear();
}
n=p;
}
void dev()
{
blo=sqrt(n);
for(ll i=1;i<=n;i++)
bl[i]=(i-1)/blo+1,ans[bl[i]].push_back(a[i]),mx=max(mx,bl[i]);
}
void update(ll L,ll R,ll x)
{
for(int i=1;i<=mx;i++)
{
if(L>ans[i].size())
L-=ans[i].size();
else
{
ans[i].insert(ans[i].begin()+L-1,R);
if(ans[i].size()>2*mx) res(),dev();//重新分块
return;
}
}
}
ll query(ll L,ll R,ll c)
{
for(int i=1;i<=mx;i++)
{
if(R>ans[i].size())
R-=ans[i].size();
else
return ans[i][R-1];
}
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
ll ty,l,r,c,m;
cin>>n;
m=n;
for(ll i=1;i<=n;i++)
cin>>a[i];
//for(ll i=1;i<=n;i++)
// bl[i]=(i-1)/blo+1;
dev();
for(ll i=0;i<m;i++)
{
cin>>ty>>l>>r>>c;
if(ty==0)
{
update(l,r,c);
}
else
{
cout<<query(l,r,c)<<endl;
}
}
//cout << "Hello world!" << endl;
return 0;
}
//操作涉及区间乘法,区间加法,单点询问。
/*
很显然,如果只有区间乘法,和分块入门 1 的做法没有本质区别,但要思考如何同时维护两种标记。
我们让乘法标记的优先级高于加法(如果反过来的话,新的加法标记无法处理)
若当前的一个块乘以m1后加上a1,这时进行一个乘m2的操作,则原来的标记变成m1*m2,a1*m2
若当前的一个块乘以m1后加上a1,这时进行一个加a2的操作,则原来的标记变成m1,a1+a2
对于不完整的块,直接 a[i] = a[i] × tg2[b[i]] + tg1[b[i]] 将这个块的所有元素都还原,
也就是将该块的标记下传(这里比较像线段树
*/
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=10007;
int n,mx;
ll a[N];
ll vis[N];
int bl[N];//分块
int blo;//分块大小
ll sum[N];//加法标记
ll pre[N];//乘法标记
ll add[N];
vector<int>ans[2000];
void init()
{
for(ll i=1;i<=n;i++)
{
add[bl[i]]+=a[i];
}
}
void res()
{
int p=0;
for(int i=1;i<=mx;i++)
{
for(int j=0;j<ans[i].size();j++)
a[++p]=ans[i][j];
ans[i].clear();
}
n=p;
}
void dev()
{
blo=sqrt(n);
for(ll i=1;i<=n;i++)
bl[i]=(i-1)/blo+1,ans[bl[i]].push_back(a[i]),mx=max(mx,bl[i]);
}
void push_down(int k)
{
for(int i=(bl[k]-1)*blo+1;i<=bl[k]*blo;i++)
a[i]=(a[i]*pre[bl[i]]%mod+sum[bl[i]]%mod)%mod;
pre[bl[k]]=1,sum[bl[k]]=0;
}
void update(int L,int R,int x)
{
if(bl[L]==bl[R])
{
push_down(L);
for(int i=L;i<=R;i++)
a[i]=(x%mod+a[i]%mod)%mod;
}
else
{
push_down(L);
for(int i=L;i<=bl[L]*blo;i++)
a[i]=(x%mod+a[i]%mod)%mod;
for(int i=bl[L]+1;i<=bl[R]-1;i++)
sum[i]=(x%mod+sum[i]%mod)%mod;
push_down(R);
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
a[i]=(x%mod+a[i]%mod)%mod;
}
}
void updates(int L,int R,int x)
{
if(bl[L]==bl[R])
{
push_down(L);
for(int i=L;i<=R;i++)
a[i]=(x%mod*a[i]%mod)%mod;
}
else
{
push_down(L);
for(int i=L;i<=bl[L]*blo;i++)
a[i]=(x%mod*a[i]%mod)%mod;
for(int i=bl[L]+1;i<=bl[R]-1;i++)
sum[i]=(x%mod*sum[i]%mod)%mod,pre[i]=(x%mod*pre[i]%mod)%mod;
push_down(R);
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
a[i]=(x%mod*a[i]%mod)%mod;
}
}
ll query(ll L,ll R,ll c)
{
return (a[R]*pre[bl[R]]%mod+sum[bl[R]]%mod)%mod;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
ll ty,l,r,c,m;
cin>>n;
m=n;
blo=sqrt(n);
for(ll i=1;i<=n;i++)
cin>>a[i];
for(ll i=1;i<=n;i++)
bl[i]=(i-1)/blo+1,pre[bl[i]]=1;
for(ll i=0;i<m;i++)
{
cin>>ty>>l>>r>>c;
if(ty==0)
{
update(l,r,c);
}
else if(ty==1)
{
updates(l,r,c);
}
else
{
cout<<query(l,r,c)<<endl;
}
}
//cout << "Hello world!" << endl;
return 0;
}
数列分块入门 8
下代码为错误的,debug4个小时都没找出来,放弃治疗了
修改的时候还记得标记,查询的时候把标记这个事情给忘了…这记性
//操作涉及区间询问等于一个数 的元素,并将这个区间的所有元素改为 。
/*
区间修改没有什么难度,这题难在区间查询比较奇怪,因为权值种类比较多,似乎没有什么好的维护方法。
模拟一些数据可以发现,询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了。
我们思考这样一个暴力,还是分块,维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。
这样看似最差情况每次都会耗费O(n)的时间,但其实可以这样分析:
假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。
换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。
初始序列不同值,经过类似分析后,就可以放心的暴力啦。
*/
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=10007;
ll n,mx;
ll a[N];
ll vis[N];
ll bl[N];//分块
ll blo;//分块大小
ll su[N];//加法标记
ll pre[N];//乘法标记
ll tag[N];//区间标记
ll add[N];
vector<ll>ans[2000];
void update(ll L,ll R,ll x)
{
if(bl[L]==bl[R])
{
if(vis[bl[L]])
{
for(ll i=(bl[L]-1)*blo+1;i<L;i++) a[i]=tag[bl[L]];
for(ll i=R+1;i<=min(bl[R]*blo,n);i++) a[i]=tag[bl[R]];
vis[bl[L]]=0;
}
for(ll i=L;i<=R;i++)
a[i]=x;
}
else
{
if(vis[bl[L]])
{
for(ll i=(bl[L]-1)*blo+1;i<L;i++) a[i]=tag[bl[L]];
vis[bl[L]]=0;
}
for(ll i=L;i<=bl[L]*blo;i++)
a[i]=x;
for(ll i=bl[L]+1;i<=bl[R]-1;i++)
tag[i]=x,vis[i]=1;
for(ll i=(bl[R]-1)*blo+1;i<=R;i++)
a[i]=x;
if(vis[bl[R]])
{
for(ll i=R+1;i<=min(bl[R]*blo,n);i++) a[i]=tag[bl[R]];
vis[bl[R]]=0;
}
}
}
ll query(ll L,ll R,ll c)
{
ll sum=0;
if(bl[L]==bl[R])
{
if(vis[bl[L]])
{
if(tag[bl[L]]==c)
sum+=R-L+1;
}
else
for(int i=L;i<=R;i++)
sum+=(ll)(a[i]==c);
}
else
{
if(vis[bl[L]])
{
if(tag[bl[L]]==c)
sum+=bl[L]*blo-L+1;
}
else
for(int i=L;i<=bl[L]*blo;i++)
sum+=(ll)(a[i]==c);
for(int i=bl[L]+1;i<=bl[R]-1;i++)
{
if(vis[i])
{
if(tag[i]==c)
sum+=blo;
}
else
{
for(int j=(i-1)*blo+1;j<=i*blo;j++)
sum+=(ll)(a[j]==c);
}
}
if(vis[bl[R]])
{
if(tag[bl[R]]==c)
sum+=R-(bl[R]-1)*blo;
}
else
for(int i=(bl[R]-1)*blo+1;i<=R;i++)
sum+=(ll)(a[i]==c);
}
return sum;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
ll ty,l,r,c,m;
cin>>n;
m=n;
blo=sqrt(n);
for(ll i=1;i<=n;i++)
cin>>a[i];
for(ll i=1;i<=n;i++)
bl[i]=(i-1)/blo+1;
for(ll i=0;i<m;i++)
{
cin>>l>>r>>c;
cout<<query(l,r,c)<<"\n";
update(l,r,c);
}
//cout << "Hello world!" << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
//莫队算法主要处理离线问题,查询只给出L,R
//当[L,R]很容易向[L-1,R],[L+1,R],[L,R-1],[L,R+1]转移时可用莫队
//注意转移的时候先扩张再收缩,L先向右,L再向左,最后再收缩
//add就是当前区间添加某元素时要做的操作
//remove就是当前区间删除某元素时要做的操作
//add,remove函数写的时候都要注意结构顺序
struct node
{
int l,r,id;
}Q[maxn];
int bl[maxn];//保存所在块
bool cmp(const node &a,const node &b)
{
if(bl[a.l]==bl[b.l]) return a.r<b.r; return bl[a.l]<bl[b.l];
}
int a[maxn];
ll ans[maxn]; //保存每个查询得答案
int L=0,R=0; //多组记得重置
ll Ans=0; //多组记得重置
void Add(int x);
void Remove(int x);
int main()
{
scanf("%d%d%d",&n,&m,&k);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=a[i]^a[i-1];
bl[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(L+1<Q[i].l) Remove(L++);
while(L+1>Q[i].l) Add(L--);
while(R<Q[i].r) Add(R++);
while(R>Q[i].r) Remove(R--);
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
带修莫队
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=2*1e6+10;
inline int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0',c=getchar();}
return x*f;
}
int N,M;
int a[MAXN],where[MAXN];
struct Query
{
int x,y,pre,id;
}Q[MAXN];
int Qnum=0;
struct Change
{
int pos,val;
}C[MAXN];
int Cnum=0;
int color[MAXN],ans=0,base,out[MAXN];
int comp(const Query &a,const Query &b)
{
if(a.x!=b.x) return where[a.x]<where[b.x];
if(a.y!=b.y) return where[a.y]<where[b.y];
return a.pre<b.pre;
}
void Add(int val)
{
if(++color[val]==1) ans++;
}
void Delet(int val)
{
if(--color[val]==0) ans--;
}
void Work(int now,int i)
{
if(C[now].pos>=Q[i].x&&C[now].pos<=Q[i].y)//注意:只有修改在查询的区间内才会对查询的结果产生影响
{
if( --color[a[C[now].pos]] == 0 ) ans--;
if( ++color[C[now].val] == 1) ans++;
}
swap(C[now].val,a[C[now].pos]);
//这里有个很巧妙的操作
//对于一个操作,下一次需要为的颜色是本次被改变的颜色
//比如,我把颜色3改为了7,那么再进行这次修改的时候就是把7改为3
//所以直接交换两种颜色就好
}
void MoQueue()
{
int l=1,r=0,now=0;
for(int i=1;i<=Qnum;i++)
{
while(l<Q[i].x) Delet(a[l++]);
while(l>Q[i].x) Add(a[--l]);
while(r<Q[i].y) Add(a[++r]);
while(r>Q[i].y) Delet(a[r--]);//以上四句为莫队模板
while(now<Q[i].pre) Work(++now,i);//改少了,改过去
while(now>Q[i].pre) Work(now--,i);//改多了,改回来
out[Q[i].id]=ans;//统计答案
}
for(int i=1;i<=Qnum;i++)
printf("%d\n",out[i]);
}
int main()
{
N=read();M=read();
base=sqrt(N);
for(int i=1;i<=N;i++) a[i]=read(),where[i]=(i-1)/base+1;
while(M--)
{
char opt[5];
scanf("%s",opt);
if(opt[0]=='Q')
{
Q[++Qnum].x=read();
Q[Qnum].y=read();
Q[Qnum].pre=Cnum;//别忘了记录最近的修改位置
Q[Qnum].id=Qnum;
}
else if(opt[0]=='R')
{
C[++Cnum].pos=read();
C[Cnum].val=read();
}
}
sort(Q+1,Q+Qnum+1,comp);//玄学排序
MoQueue();
return 0;
}