数列分块
1. 区间加法,区间求和
简单分析一下:将数列划分为 个块,对于区间修改,整块的进行atag标记一下,非整块的最多只有 个,暴力加一下
复杂度:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];
void add(int a,int b,int c)
{
for(int i=a;i<=min(b,block*bl[a]);i++)
{
v[i]+=c;
}
if(bl[a]!=bl[b])
{
for(int i=block*(bl[b]-1)+1;i<=b;i++)
{
v[i]+=c;
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
atag[i]+=c;
}
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
mem(atag,0);
for(int i=1;i<=n;i++)v[i]=read();
for(int i=1;i<=n;i++)bl[i]=(i-1)/block+1;
for(int i=1;i<=n;i++)
{
f=read();
if(f==0)
{
a=read();b=read();c=read();
add(a,b,c);
}
if(f==1)
{
a=read();
printf("%d\n",v[a]+atag[bl[a]]);
}
}
return 0;
}
2. 区间加法,询问小于某个数的个数。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
#include<algorithm>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];
vector<int >ve[maxn];
void add(int a,int b,int c)
{
for(int i=a;i<=min(b,block*bl[a]);i++)
{
v[i]+=c;
}
if(bl[a]!=bl[b])
{
for(int i=bl[b]*(block-1)+1;i<=b;i++)
{
v[i]+=c;
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
atag[i]+=c;
}
}
int query(int a,int b,int x)
{
int ans=0;
for(int i=1;i<=min(bl[a]*block,b);i++)
{
if(v[i]+atag[bl[a]]<x)
{
ans++;
}
}
if(bl[a]!=bl[b])
for(int i=block*(bl[b]-1)+1;i<=b;i++)
{
if(v[i]+atag[bl[b]]<x)ans++;
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
x-=atag[i];
ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();
}
return ans;
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
mem(atag,0);
for(int i=1;i<=n;i++)
{
v[i]=read();
bl[i]=(i-1)/block+1;
}
for(int i=1;i<=n;i++)
{
ve[bl[i]].push_back(v[i]);
}
for(int i=1;i<=bl[n];i++)
{
sort(ve[i].begin(),ve[i].end());
}
for(int i=1;i<=n;i++)
{
f=read();
if(f==0)
{
a=read();b=read();c=read();
add(a,b,c);
}
if(f==1)
{
a=read();b=read();c=read();
printf("%d\n",query(a,b,c));
}
}
return 0;
}
/*
9
1 3 2 5 7 9 4 6 8
0 1 4 1
1 1 5 4
*/
3. 区间加法,询问区间内小于某个值x的前驱(小于x的最大值)
做法和2一样,改变一下,query即可
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
#include<algorithm>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];
vector<int >ve[maxn];
void add(int a,int b,int c)
{
for(int i=a;i<=min(b,block*bl[a]);i++)
{
v[i]+=c;
}
if(bl[a]!=bl[b])
{
for(int i=bl[b]*(block-1)+1;i<=b;i++)
{
v[i]+=c;
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
atag[i]+=c;
}
}
int query(int a,int b,int x)
{
int ans=0;
for(int i=1;i<=min(bl[a]*block,b);i++)
{
if(v[i]+atag[bl[a]]<x)
{
ans=max(ans,v[i]);
}
}
if(bl[a]!=bl[b])
for(int i=block*(bl[b]-1)+1;i<=b;i++)
{
if(v[i]+atag[bl[b]]<x)ans=max(ans,v[i]);
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
x-=atag[i];
int k=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();
ans=max(ans,v[k-1]);
}
return ans;
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
mem(atag,0);
for(int i=1;i<=n;i++)
{
v[i]=read();
bl[i]=(i-1)/block+1;
}
for(int i=1;i<=n;i++)
{
ve[bl[i]].push_back(v[i]);
}
for(int i=1;i<=bl[n];i++)
{
sort(ve[i].begin(),ve[i].end());
}
for(int i=1;i<=n;i++)
{
f=read();
if(f==0)
{
a=read();b=read();c=read();
add(a,b,c);
}
if(f==1)
{
a=read();b=read();c=read();
printf("%d\n",query(a,b,c));
}
}
return 0;
}
/*
9
1 3 2 5 7 9 4 6 8
0 1 4 1
1 1 5 4
*/
4.区间加法,区间区和
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn],sum[maxn];
void add(int a,int b,int c)
{
for(int i=a;i<=min(b,block*bl[a]);i++)
{
v[i]+=c;sum[bl[a]]+=c;
}
if(bl[a]!=bl[b])
{
for(int i=block*(bl[b]-1)+1;i<=b;i++)
{
v[i]+=c;sum[bl[b]]+=c;
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
atag[i]+=c;
}
}
int query(int a,int b)
{
int ans=0;
for(int i=a;i<=min(block*bl[a],b);i++)
{
ans+=v[i]+atag[bl[a]];
}
if(bl[a]!=bl[b])
{
for(int i=block*(bl[b]-1)+1;i<=b;i++)
{
ans+=v[i]+atag[bl[b]];
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
ans+=sum[i]+atag[i]*block;
}
return ans;
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
mem(atag,0);
for(int i=1;i<=n;i++)v[i]=read();
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/block+1;
sum[bl[i]]+=v[i];
}
for(int i=1;i<=n;i++)
{
f=read();
if(f==0)
{
a=read();b=read();c=read();
add(a,b,c);
}
if(f==1)
{
a=read(),b=read();
printf("%d\n",query(a,b));
}
}
return 0;
}
5.区间开方求和
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn],sum[maxn],flag[maxn];
void solve_sqrt(int x)
{
if(flag[x])return ;
flag[x]=1;
sum[x]=0;
for(int i=(x-1)*block+1;i<=x*block;i++)
{
v[i]=sqrt(v[i]);
sum[x]+=v[i];
if(v[i]>1)flag[x]=0;
}
}
void add(int a,int b)
{
for(int i=a;i<=min(b,block*bl[a]);i++)
{
sum[bl[a]]-=v[i];
v[i]=sqrt(v[i]);
sum[bl[a]]+=v[i];
}
if(bl[a]!=bl[b])
{
for(int i=block*(bl[b]-1)+1;i<=b;i++)
{
sum[bl[b]]-=v[i];
v[i]=sqrt(v[i]);
sum[bl[b]]+=v[i];
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
solve_sqrt(i);
}
}
int query(int a,int b)
{
int ans=0;
for(int i=a;i<=min(block*bl[a],b);i++)
{
ans+=v[i];
}
if(bl[a]!=bl[b])
{
for(int i=block*(bl[b]-1)+1;i<=b;i++)
{
ans+=v[i];
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
ans+=sum[i];
}
return ans;
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
mem(atag,0);
for(int i=1;i<=n;i++)v[i]=read();
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/block+1;
sum[bl[i]]+=v[i];
}
for(int i=1;i<=n;i++)
{
f=read();
if(f==0)
{
a=read();b=read();
add(a,b);
}
if(f==1)
{
a=read(),b=read();
printf("%d\n",query(a,b));
}
}
return 0;
}
6.单点插入,单点询问
注意:这可能导致某个块数量剧增,因此引入重构
重构:1)每 次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为 ,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。
2)当然,也可以当某个块过大时重构,或者只把这个块分成两半。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];
int st[2*maxn],top,m;
vector<int> ve[maxn];
void rebuild()
{
top=0;
for(int i=1;i<=m;i++)
{
for(vector<int>::iterator j=ve[i].begin();j!=ve[i].end();j++)
{
st[++top]=*j;
}
ve[i].clear();
}
int blo2=sqrt(top);
for(int i=1;i<=top;i++)
{
ve[(i-1)/blo2+1].push_back(st[i]);
}
m=(top-1)/blo2+1;
}
pair<int ,int> query(int x)
{
int a=1;
while(x>ve[a].size())
{
x-=ve[a].size();
a++;
}
return make_pair(a,x-1);
}
void insert(int a,int b)
{
pair<int ,int> t=query(a);
ve[t.first].insert(ve[t.first].begin()+t.second,b);
if(ve[t.first].size()>20*block)rebuild();
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
mem(atag,0);
for(int i=1;i<=n;i++)v[i]=read();
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/block+1;
ve[bl[i]].push_back(v[i]);
}
for(int i=1;i<=n;i++)
{
f=read();
if(f==0)
{
a=read();b=read();
insert(a,b);
}
if(f==1)
{
a=read();
pair<int,int> t=query(a);
printf("%d\n",ve[t.first][t.second]);
}
}
return 0;
}
7.区间加法,区间乘法,单点查值
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=50005;
int v[maxn],atag[maxn],mtag[maxn],bl[maxn],block,n;
ll read()
{
int f=1,x=0;
char c;c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
return x*f;
}
void reset(int x)
{
for(int i=(x-1)*block+1;i<=min(x*block,n);i++)//注意终点
{
v[i]=v[i]*mtag[x]+atag[x];
}
mtag[x]=1;atag[x]=0;
}
void add(int f,int a,int b,int c)
{
reset(bl[a]);
for(int i=a;i<=min(bl[a]*block,b);i++)
{
if(f==0)//加
v[i]+=c;
else v[i]*=c;
}
if(bl[a]!=bl[b])
{
reset(bl[b]);
for(int i=(bl[b]-1)*block+1;i<=b;i++)
{
if(f==0)v[i]+=c;
else v[i]*=c;
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
if(f==0)
{
atag[i]+=c;
}
else
{
atag[i]=c*atag[i];
mtag[i]=mtag[i]*c;
}
}
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
for(int i=1;i<=n;i++)mtag[i]=1;
for(int i=1;i<=n;i++)
{
v[i]=read();
bl[i]=(i-1)/block+1;
}
for(int i=1;i<=n;i++)
{
f=read();
if(f==2)//单点查询
{
a=read();
printf("%d\n",v[a]*mtag[bl[a]]+atag[bl[a]]);
}
else{
a=read();b=read();c=read();
add(f,a,b,c);
}
}
return 0;
}
8.区间查询c的个数,并将区间所有值更新为c
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=50005;
int v[maxn],bl[maxn],tag[maxn],n,block;
ll read()
{
int f=1,x=0;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return f*x;
}
void reset(int x)
{
if(tag[x]==-1)return;
for(int i=(x-1)*block+1;i<=min(block*x,n);i++)
{
v[i]=tag[x];
}
tag[x]=-1;
}
int query(int a,int b,int c)
{
int ans=0;
reset(bl[a]);
for(int i=a;i<=min(b,block*bl[a]);i++)
{
if(v[i]==c)ans++;
else v[i]=c;
}
if(bl[a]!=bl[b])
{
reset(bl[b]);
for(int i=(bl[b]-1)*block+1;i<=b;i++)
{
if(v[i]==c)ans++;
else v[i]=c;
}
}
for(int i=bl[a]+1;i<=bl[b]-1;i++)
{
if(tag[i]!=-1)
{
if(tag[i]!=c)tag[i]=c;
else ans+=block;
}
else{
for(int j=(i-1)*block+1;j<=i*block;j++)
{
if(v[j]==c)ans++;
else v[j]=c;
}
tag[i]=c;
}
}
return ans;
}
int main()
{
int f,a,b,c;
n=read();block=sqrt(n);
for(int i=1;i<=n;i++)
{
v[i]=read();
bl[i]=(i-1)/block+1;
}
memset(tag,-1,sizeof tag);
for(int i=1;i<=n;i++)
{
a=read();b=read();c=read();
printf("%d\n",query(a,b,c));
}
return 0;
}
/*
9
1 3 2 5 7 9 4 6 8
*/
9.求区间众数
这是一道相对较难的题处理过程:首先处理要查询的区间连续整块区间的的众数ans,然后处理非整块的2*block个数。
然后思考两个问题:1).连续整块区间的众数会是整个区间的众数吗?(显然能取出反例)
2).如果连续整块区间的众数不能作为整块区间的众数,那么我们处理它有什么用?
回答:如果ans不是众数,那么必然存在另一个数是众数,并且这个众数必然在另 外的2*block个数中出现过,那个枚举可能成为众数的2*block个数与ans比较即可。
具体见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const int maxn=50005;
int n,block,v[maxn],bl[maxn],id ;
int f[505][505];//记录从块i到块j的众数的val的下标(也是v[i])
map<int,int >mp;
int val[maxn],cnt[maxn];
vector<int>ve[maxn];
ll read()
{
int f=1,x=0;
char c;c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
return x*f;
}
void pre(int x)
{
memset(cnt,0,sizeof cnt);
int t=0,mx=0;
for(int i=(x-1)*block+1;i<=n;i++)
{
cnt[v[i]]++;
if((cnt[v[i]]>mx)||(cnt[v[i]]==mx&&val[v[i]]<val[t]))
{
t=v[i];
mx=cnt[v[i]];
}
f[x][bl[i]]=t;
}
}
int query(int l,int r,int x)
{
return upper_bound(ve[x].begin(),ve[x].end(),r)-lower_bound(ve[x].begin(),ve[x].end(),l);
}
int query(int a,int b)
{
int ans=f[bl[a]+1][bl[b]-1];
int mx=query(a,b,ans);
int t;
for(int i=a;i<=min(bl[a]*block,b);i++)
{
t=query(a,b,v[i]);
if((t>mx)||(t==mx&&val[v[i]]<val[ans]))
{
ans=v[i];
mx=t;
}
}
if(bl[a]!=bl[b])
{
for(int i=(bl[b]-1)*block+1;i<=b;i++)
{
t=query(a,b,v[i]);
if((t>mx)||(t==mx&&val[v[i]]<val[ans]))
{
ans=v[i];
mx=t;
}
}
}
return ans;
}
int main()
{
int a,b;
n=read();block=200;id=0;
for(int i=1;i<=n;i++)
{
v[i]=read();
if(!mp[v[i]])
{
mp[v[i]]=++id;
val[id]=v[i];
}
v[i]=mp[v[i]];
ve[v[i]].push_back(i);
}
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/block+1;
}
for(int i=1;i<=bl[n];i++)pre(i);
for(int i=1;i<n;i++)
{
a=read();b=read();
if(a>b)swap(a,b);
printf("%d\n",val[query(a,b)]);
}
return 0;
}