數列分塊
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;
}