**总述:**数列分块在我的理解中,它能完成区间的许多看似复杂的操作,而树状数组和线段树对于有些操作也只能望而却步,在这里不做过多解释,每种题型的大体代码模块差不多,不过每个题有每个题的特点,有些细节和优化操作还是很妙的,这是一项比较巨大的工程,如有需要请看目录啦。
传送门
题意:给定n个数,完成n次操作,操作之一是将位于[l,r]的之间的数字都加c,之二是询问a[r] 的值(l和c忽略)
树状数组
解题思路:这里就不做过多解释了,毕竟是数列分块的主场~~~
Code:
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6+10;
typedef pair<int,int> PII;
map<string,int>mp;
PII q[100000];
int n,m;
int a[maxn]={0},b[maxn],c[maxn];
int lowbit(int x){
return x&(-x);
}
void update(int i,int k){
while(i<=n){
c[i]+=k;
i+=lowbit(i);
}
}
int getsum(int i){
int res=0;
while(i>0){
res+=c[i];
i-=lowbit(i);
}
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
update(i,a[i]-a[i-1]);
}
for(int i=1;i<=n;i++)
{
int opt,l,r,c1;
scanf("%d%d%d%d",&opt,&l,&r,&c1);
if(opt==0)
{
update(l,c1);
update(r+1,-c1);
}
else{
printf("%d\n",getsum(r));
}
}
return 0;
}
线段树Code:
解题思路:先解释一下概念,完整块:长度为n/m的块,不完整快:长度小于block的块,一般就是最后一个块;对于一个完整块区间同时加一个值的话,我们可以用一个懒惰标记记录一下,不完整快的话就只能暴力更新了,假设每个块的数量是n/m,暴力更新的复杂度最高是O(n/m),懒惰标记的复杂度是O(m),总的复杂度就是O(n/m+m),根据均值不等式,当每个块的数量是sqrt(n)时,复杂度最小,细节解释见代码
Code:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];///原数组
int belong[maxn]; ///第i个元素属于哪一块
int lazy[maxn];///每块额外的懒惰标记
int sum[maxn];///每块的和
vector<int> v[1010];
void build(){ ///初始化
block=(int)sqrt(n);
num=n/block;
if(n%block) 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,sum[belong[i]]+=a[i];///第i个数所属的块及每块的和
}
void update(int x,int y,int c){
if(belong[x]==belong[y]) ///x和y属于同一块时
{
for(int i=x;i<=y;i++) ///暴力更新
a[i]+=c;
sum[belong[x]]+=(y-x+1)*c; ///区间和更新
return ;
}
for(int i=x;i<=r[belong[x]];i++) ///不完整块,暴力更新
a[i]+=c;
sum[belong[x]]+=(r[belong[x]]-x+1)*c; ///区间和更新
for(int i=belong[x]+1;i<belong[y];i++) ///完整块,懒惰标记更新
lazy[i]+=c;
for(int i=l[belong[y]];i<=y;i++) ///不完整块,暴力更新
a[i]+=c;
sum[belong[y]]+=(y-l[belong[y]]+1)*c; ///区间和更新
}
int query(int x){
return a[x]+lazy[belong[x]]; ///结果是本身加上懒惰标记
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build();
while(n--){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) update(l,r,c); ///更新操作
else printf("%d\n",query(r)); ///查询操作
}
return 0;
}
传送门
题意: n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的元素个数
解题思路:既然要询问到小于某个数的个数,那就要涉及到排序问题了,对于不完整块,暴力查找;对于完整块,用二分查找或使用stl中的lower_bound都可;前提是每一块都要放在对应的容器vector中;这里有一个问题是在一个完整块中加上一个数时,不改变块内元素的相对大小,所以不需要重新排序;如果是不完整块,那就要暴力更新,重新排序啦,细节解释见代码
Code:
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn =1e6 + 10;
int n,m;
int block;///块的数量
int num;///每块中的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn]; ///原数组
int lazy[maxn]; ///懒惰标记
int belong[maxn]; ///第i个元素属于哪一块
vector<int> v[1010];
void build(){
block=sqrt(n);
num=n/block;
if(n%block) 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;
v[belong[i]].push_back(a[i]); ///将a[i]放入第i个数属于的第belong[i]块容器中
}
for(int i=1;i<=num;i++)
{
sort(v[i].begin(),v[i].end()); ///对每块中元素排序
}
}
void resort(int u){ ///重新排序
v[u].clear();
for(int i=l[u];i<=r[u];i++) v[u].push_back(a[i]);
sort(v[u].begin(),v[u].end());
}
void update(int x,int y,int c){
if(belong[x]==belong[y]) ///x和y属于同一块
{
for(int i=x;i<=y;i++) a[i]+=c; ///暴力更新
resort(belong[x]); ///重新排序
return ;
}
for(int i=x;i<=r[belong[x]];i++) a[i]+=c;
for(int i=belong[x]+1;i<belong[y];i++) lazy[i]+=c; ///更新懒惰标记,不用重新排序
for(int i=l[belong[y]];i<=y;i++) a[i]+=c;
resort(belong[x]);
resort(belong[y]);
}
int query(int x,int y,int c){
int ans=0;
if(belong[x]==belong[y])
{
for(int i=x;i<=y;i++)
if(a[i]+lazy[belong[i]]<c) ans++; ///暴力判断,莫忘懒惰标记
return ans;
}
for(int i=x;i<=r[belong[x]];i++)
if(a[i]+lazy[belong[i]]<c) ans++; ///暴力判断,莫忘懒惰标记
for(int i=belong[x]+1;i<belong[y];i++)
ans+=lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin(); ///查找每一块中满足题意得元素个数
for(int i=l[belong[y]];i<=y;i++)
if(a[i]+lazy[belong[i]]<c) ans++; ///暴力判断,莫忘懒惰标记
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build();
while(n--){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0)
update(l,r,c);
else
printf("%d\n",query(l,r,c*c));
}
return 0;
}
传送门
题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)
解题思路:这里要询问某个元素的前驱,和上一个题思路差不多,也是需要排序加二分或lower_bound查找,没啥不同的,细节解释见代码
Code:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn]; ///原序列
int belong[maxn]; ///第i个元素属于哪一块
int tot=0;
int lazy[maxn]; ///懒惰标记
vector<int> v[1010];
void build(){
block=(int)sqrt(n);
num=n/block;
if(n%block) 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,v[belong[i]].push_back(a[i]); ///加入元素
for(int i=1;i<=num;i++) sort(v[i].begin(),v[i].end()); ///初始排序
}
void resort(int x){
v[x].clear();
for(int i=l[x];i<=r[x];i++)
v[x].push_back(a[i]);
sort(v[x].begin(),v[x].end());
}
void update(int x,int y,int c){
if(belong[x]==belong[y])
{
for(int i=x;i<=y;i++) a[i]+=c;
resort(belong[x]);
return ;
}
for(int i=x;i<=r[belong[x]];i++) a[i]+=c;
resort(belong[x]);
for(int i=belong[x]+1;i<belong[y];i++) lazy[i]+=c;
for(int i=l[belong[y]];i<=y;i++) a[i]+=c;
resort(belong[y]);
}
int query(int x,int y,int c){
int ans=-1;
if(belong[x]==belong[y])
{
for(int i=x;i<=y;i++)
{
if(a[i]+lazy[belong[x]]<c)
{
ans=max(ans,a[i]+lazy[belong[x]]); ///不完整块,暴力查找答案,莫忘懒惰标记
}
}
return ans;
}
for(int i=x;i<=r[belong[x]];i++)
{
if(a[i]+lazy[belong[x]]<c)
ans=max(ans,a[i]+lazy[belong[x]]);
}
for(int i=belong[x]+1;i<belong[y];i++)
{
int pos=lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin();///pos记录答案的位置,只有pos>=1时,答案才存在,vector的第一个元素的下标
if(pos>=1) ans=max(ans,v[i][pos-1]+lazy[i]);
}
for(int i=l[belong[y]];i<=y;i++)
{
if(a[i]+lazy[belong[y]]<c)
ans=max(ans,a[i]+lazy[belong[y]]);
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build();
while(n--){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) update(l,r,c);
else printf("%d\n",query(l,r,c));
}
return 0;
}
传送门
题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,区间求和
树状数组Code:
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e6 + 10;
typedef long long ll;
ll n,m,c;
ll a[maxn],sum1[maxn],sum2[maxn];
ll lowbit(ll x){
return x&(-x);
}
void update(ll sum[],ll x,ll c){
for(ll i=x;i<=n;i+=lowbit(i)) sum[i]+=c;
}
ll sums(ll sum[],ll x){
ll res=0;
for(ll i=x;i>0;i-=lowbit(i)) res=(res+sum[i]);
return res;
}
ll get_sum(ll x){
return sums(sum1,x)*(x+1)-sums(sum2,x);
}
int main(){
scanf("%lld",&n);
for(ll i=1;i<=n;i++) scanf("%d",&a[i]);
for(ll i=1;i<=n;i++)
{
ll b=a[i]-a[i-1];
update(sum1,i,b);
update(sum2,i,i*b);
}
for(ll i=1;i<=n;i++){
ll opt,l,r;
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(opt==0){
update(sum1,l,c);
update(sum1,r+1,-c);
update(sum2,l,l*c);
update(sum2,r+1,(r+1)*(-c));
}
else{
printf("%lld\n",(get_sum(r)-get_sum(l-1))%(c+1));
}
}
return 0;
}
线段树Code:
解题思路:用一个和数组维护好每个块的元素和就好了,我也没清楚这里的懒惰标记数组为啥要开long long,卡了好久,细节解释见代码
Code:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];///原数组
int belong[maxn]; ///第i个元素属于哪一块
ll lazy[maxn];///每块额外的和标记
ll sum[maxn];///每块的和
void build(){
block=(int)sqrt(n);
num=n/block;
if(n%block) 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;
sum[belong[i]]+=a[i]; ///初始化每一块的和
}
}
void update(int x,int y,int c){
if(belong[x]==belong[y])
{
for(int i=x;i<=y;i++)
a[i]+=c;
sum[belong[x]]+=(y-x+1)*c; ///更新区间和
return ;
}
for(int i=x;i<=r[belong[x]];i++)
a[i]+=c;
sum[belong[x]]+=(r[belong[x]]-x+1)*c;///更新区间和
for(int i=belong[x]+1;i<belong[y];i++)
lazy[i]+=c; ///更新懒惰标记
for(int i=l[belong[y]];i<=y;i++)
a[i]+=c;
sum[belong[y]]+=(y-l[belong[y]]+1)*c;///更新区间和
}
int query(int x,int y,int c){
int ans=0;
if(belong[x]==belong[y])
{
for(int i=x;i<=y;i++)
ans=(ans+a[i]+lazy[belong[i]])%c; ///莫忘懒惰标记
return ans;
}
for(int i=x;i<=r[belong[x]];i++)
ans=(ans+a[i]+lazy[belong[i]])%c;
for(int i=belong[x]+1;i<belong[y];i++)
ans=(ans+sum[i]+lazy[i]*block)%c; ///整个块的懒惰标记和就是lazy[i]*block
for(int i=l[belong[y]];i<=y;i++)
ans=(ans+a[i]+lazy[belong[i]])%c;
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build();
while(n--){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) update(l,r,c);
else printf("%d\n",query(l,r,c+1));
}
return 0;
}
传送门
题意:给出一个长为 n 的数列 a1,a2,…,an,以及 n 个操作,操作涉及区间开方,区间求和
解题思路:用普通的方法做肯定超时,每个区间开平方的操作没有规律可循,也没法标记;这里就有一个小细节,观察数据范围,每个数最多开方5~6次就会变为1,最后的数组中大部分就是1或0,于是我们可以对为1或0的区间打上标记,再遇到开方操作,直接跳过即可,实在是妙啊,细节解释见代码
Code:
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn =1e6 + 10;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn]; ///原数列
int sum[maxn]; ///记录区间和
int belong[maxn]; ///第i个元素属于哪一块
bool vis[maxn]; ///标记数组(某一块全部为0或全部为1或部分为1,部分为0)
vector<ll> v[1000];
void build(){
block=sqrt(n);
num=n/block;
if(n%block) 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;
sum[belong[i]]+=a[i];
}
}
void update(int x,int y){
int temp=0;
if(belong[x]==belong[y])
{
if(vis[belong[x]]) return ; ///如果该区间被标记,直接返回
for(int i=x;i<=y;i++)
{
temp+=a[i]-(int)sqrt(a[i]); ///单点修改
a[i]=(int)sqrt(a[i]);
}
sum[belong[x]]-=temp; ///区间和更新
return ;
}
if(!vis[belong[x]])
{
for(int i=x;i<=r[belong[x]];i++)
{
temp+=a[i]-(int)sqrt(a[i]);
a[i]=(int)sqrt(a[i]);
}
sum[belong[x]]-=temp;
}
for(int i=belong[x]+1;i<belong[y];i++)
{
if(vis[i]) continue;
temp=0;
for(int j=l[i];j<=r[i];j++)
{
temp+=a[j]-(int)sqrt(a[j]);
a[j]=(int)sqrt(a[j]);
}
if(temp==0) vis[i]=true;///此操作为整个代码中唯一更新标记数组的地方,比较巧妙,twmp==0时,表示此区间可以被标记
sum[i]-=temp;
}
temp=0;
if(!vis[belong[y]])
{
for(int i=l[belong[y]];i<=y;i++)
{
temp+=a[i]-(int)sqrt(a[i]);
a[i]=(int)sqrt(a[i]);
}
sum[belong[y]]-=temp;
}
}
int query(int x,int y){
int 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=belong[x]+1;i<belong[y];i++) ans+=sum[i];
for(int i=l[belong[y]];i<=y;i++) ans+=a[i];
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) cin >> a[i];
build();
while(n--){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0)
update(l,r);
else
printf("%d\n",query(l,r));
}
return 0;
}
传送门
题意:出一个长为 n 的数列,以及 n 个操作,操作涉及单点插入,单点询问,数据随机生成
解题思路:对于单点插入的话,我们可以把每个块的元素放到一个二维数组中,每次插入的时候就直接暴力插入,该块后面的元素后移;这里有一个问题,如果在一个地方多次插入元素,那么复杂度就会非常高,所以还要涉及到重新分块,每当插入元素的数量等于一个块的大小时,就重新分块,确保复杂度可以控制,细节解释见代码
Code:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[1010][3010]; ///用来存放元素的二维数组
int b[1010][3010]; ///重新分块时暂时储存元素的数组
int belong[maxn]; ///第i个元素属于哪一块
int tot=0; ///记录插入的数的数量是否达到block
int len[maxn]; ///表示每个块的长度
void build(){
m=n;
block=(int)sqrt(n);
num=n/block;
if(n%block) num++;
for(int i=1;i<=n;i++)
{
int kuai=(i-1)/block+1;
int x;
scanf("%d",&x);
a[kuai][++len[kuai]]=x; ///向二维数组中添加元素
}
}
void rebuild(){ ///重新分块
m+=tot; ///m记录此时总元素的数量
tot=0; ///更新tot
block=(int)sqrt(m);
int cnt=0,lenth=0;
for(int i=1;i<=num;i++)
{
for(int j=1;j<=len[i];j++)
{
cnt++;
int id=(cnt-1)/block+1;
b[id][++lenth]=a[i][j]; ///b数组暂时存放
if(lenth==block) lenth=0; ///更新lenth
}
}
num=m/block; if(m%block) num++;
for(int i=1;i<=num;i++)
{
if(i!=num||m%block==0) len[i]=block; ///更新len数组
else len[i]=m-block*block;
for(int j=1;j<=len[i];j++)
a[i][j]=b[i][j]; ///元素从b数组向a数组转移
}
}
void update(int x,int y){
int i,pos=0;
for(i=1;i<=num;i++) ///找到插入元素的位置
{
if(pos+len[i]>=x) break;
pos+=len[i];
}
len[i]++; ///更新该块的长度
for(int j=len[i];j>=x-pos+1;j--)
a[i][j]=a[i][j-1]; ///该块中元素后移
a[i][x-pos]=y; ///插入元素
tot++; ///插入的元素+1
if(tot==block) rebuild(); ///判断是否需要重新分块
}
int query(int x){
int i,pos=0;
for(i=1;i<=num;i++) ///查找答案的位置
{
if(pos+len[i]>=x) break;
pos+=len[i];
}
return a[i][x-pos]; ///返回答案
}
int main(){
scanf("%d",&n);
build();
int sum=n;
while(sum--){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) update(l,r);
else printf("%d\n",query(r));
}
return 0;
}
传送门
题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间乘法,区间加法,单点询问
解题思路:加乘混合运算的话,需要确定好优先级,同时维护好两个运算的标记数组;对于完整块,加法运算直接加到它的加法标记数组中去就可,乘法运算需要同时对该块的乘法标记和加法标记更新;对于不完整块,无论是加法还是乘法,需要先下推此块的加法标记和乘法标记,再暴力更新即可,细节解释见代码
Code:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn]; ///原数组
int mul[maxn],add[maxn]; ///乘法标记数组和加法数组
int belong[maxn]; ///第i个元素属于哪一块
void build(){
block=(int)sqrt(n);
num=n/block;
if(n%block) 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;
}
void pushdown(int x){ ///下推标记
for(int i=l[x];i<=r[x];i++)
a[i]=(a[i]*mul[x]%mod+add[x])%mod; ///暴力更新,同时下推加法标记数组和乘法标记数组
mul[x]=1; ///更新乘法标记数组
add[x]=0; ///更新加法标记数组
}
void update(int opt,int x,int y,ll c){
if(opt==0){ ///加法运算
if(belong[x]==belong[y])
{
pushdown(belong[x]); ///不完整块先下推标记
for(int i=x;i<=y;i++)
a[i]=(a[i]+c)%mod; ///暴力更新
return ;
}
pushdown(belong[x]); ///下推标记
for(int i=x;i<=r[belong[x]];i++)
a[i]=(a[i]+c)%mod;
for(int i=belong[x]+1;i<belong[y];i++)
add[i]=(add[i]+c)%mod; ///更新加法标记数组
pushdown(belong[y]); ///下推标记
for(int i=l[belong[y]];i<=y;i++)
a[i]=(a[i]+c)%mod;
}
else{
if(belong[x]==belong[y]) ///不完整块先下推标记
{
pushdown(belong[x]);
for(int i=x;i<=y;i++)
a[i]=a[i]*c%mod;
return ;
}
pushdown(belong[x]); ///下推标记
for(int i=x;i<=r[belong[x]];i++)
a[i]=(a[i]*c)%mod;
for(int i=belong[x]+1;i<belong[y];i++)
mul[i]=(mul[i]*c)%mod,add[i]=(add[i]*c)%mod; ///做乘法的时候同时更新乘法标记数组和加法标记数组
pushdown(belong[y]); ///下推标记
for(int i=l[belong[y]];i<=y;i++)
a[i]=(a[i]*c)%mod;
}
}
int query(int x){
return (a[x]%mod*mul[belong[x]]%mod+add[belong[x]])%mod; ///求答案
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),mul[i]=1; ///更新乘法标记数组
build();
m=n;
while(m--){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0||opt==1)
update(opt,l,r,c);
else
printf("%d\n",query(r));
}
return 0;
}
传送门
**题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c **
解题思路:其中一个操作是将整个区间的元素修改,我们可以想到可以用一个bool数组记录每个块的整体修改状态,再加上一个懒惰标记该块修改后的值,注意bool数组和懒惰标记是同步的;对于完整块,直需要修改该块的整体信息;不完整块,要先下推标记,再暴力更新,细节解释见代码
Code:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 10007;
int n,m;
int block;///每块中的数量
int num;///块的数量
int l[maxn],r[maxn];///每块的左边界,右边界
int a[maxn];
int belong[maxn]; ///第i个元素属于哪一块
bool vis[maxn]; ///标记某块有没有被完全修改
int lazy[maxn]; ///记录该快要被修改后的值
void build(){
block=(int)sqrt(n);
num=n/block;
if(n%block) 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;
}
void pushdown(int x){
if(vis[x]==false) return ; ///如果该块没有被完全更新的标记,直接返回
for(int i=l[x];i<=r[x];i++) a[i]=lazy[x]; ///暴力更新
vis[x]=false; ///更新bool数组
lazy[x]=1e9; ///更新懒惰标记
}
int update(int x,int y,ll c){
int ans=0;
if(belong[x]==belong[y])
{
pushdown(belong[x]); ///先下推标记
for(int i=x;i<=y;i++)
{
if(a[i]==c) ans++;
a[i]=c;
}
return ans;
}
pushdown(belong[x]);
for(int i=x;i<=r[belong[x]];i++)
{
if(a[i]==c) ans++;
else a[i]=c;
}
pushdown(belong[y]);
for(int i=l[belong[y]];i<=y;i++)
{
if(a[i]==c) ans++;
else a[i]=c;
}
for(int i=belong[x]+1;i<belong[y];i++)
{
if(vis[i]) ///如果该块被标记
{
if(lazy[i]==c) ans+=block; ///如果标记正好是c,答案直接加block
lazy[i]=c;
}
else{
for(int j=l[i];j<=r[i];j++) ///否则暴力更新求解
{
if(a[j]==c) ans++;
else a[j]=c;
}
vis[i]=true; ///更新bool数组
lazy[i]=c; ///更新懒惰标记
}
}
return ans;
}
int main(){
scanf("%d",&n);
memset(vis,false,sizeof vis);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),lazy[i]=1e9; ///初始化一下懒惰标记
build();
m=n;
while(m--){
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
printf("%d\n",update(l,r,c));
}
return 0;
}
待更–