ps: 2020年南昌理工ACM暑期集训时期所写
1.分块算法
优美的暴力:分块
1.分块:顾名思义,就是把待处理的整个大区间分成若干块。
2.口诀是:块外暴力,块内查表。
3.那么这个块的大小应该怎么分呢?应该是sqrt(n)大小。证明就不说了自己查资料。
4.自我感觉,分块很巧妙的把各种复杂度都向sqrt(N)靠近发现很多的题,都是时间复杂度n根号n,空间复杂度也是n根号n,而且不管是什么操作,基本上都是根号n。既没有n^2,也不存在O(1),感觉,巧妙地把复杂度平均了一下。
5.分块虽然是暴力,但是是一种非常有水平的暴力。关键是状态的设计,怎样达到块外暴力,块内查表。大概的感觉是,都要围绕块来进行设计。而且,通常要有两个以上函数状态有机配合。
6.对于很多的题目,我们都可以找到n^2 的暴力算法。但是,当n在10000到200000之间的时候, n^2 基本稳稳卡掉。发现,这样的题目,经常还与区间有关系的时候,可以考虑分块做法
下面展示的代码只是分块的区间添加,单点查询,区间最值做法,但已经很具代表模板了,而且注释很详尽。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1<<20;
int a[N],k,len,n;//a[N]记录数列,k指块的数目,len指块的长度
int L[N],R[N],F[N],add[N],Max[N];
// L[N]记录每个块起始元素的下标,R[N]记录每个块末尾元素下标
// F[N]记录每一个元素所属哪一个块,add[N]加法标记
// Max[N]记录每个区间的最大值
inline void Build(){ //建块
memset(a,0,sizeof(a));
memset(add,0,sizeof(add));
memset(Max,0,sizeof(Max));
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
len = sqrt(n);k = n/len; //计算块的长度和数目
if(n%k) k++; //特判最后一个不完整块
for(int i = 1; i <= k; i++)
R[i] = i*len,L[i]=R[i-1]+1; //计算每一个块的起始末尾元素下标
R[k] = n;
for(int i = 1; i <= k; i++)
for(int j = L[i]; j <= R[i]; j++){
F[j] = i; //计算每一个元素所属哪一个块
Max[i] = max(Max[i],a[j]); //计算该区间最大值
}
}
inline int Ask(int x){return a[x]+add[F[x]];} //单点询问
inline void Add(int x,int y,int z){ //区间加法
if(F[x]==F[y]){ //如果区间被包含于一个整块
for(int i = x; i <= y; i++) a[i]+=z;
return;
}
//如果区间跨过整个块
for(int i = x; i <= R[F[x]]; i++)a[i]+=z;
for(int i = L[F[y]]; i <= y; i++)a[i]+=z;
for(int i = F[x]+1; i < F[y]; i++)add[i]+=z;
}
void update(int x,int v){ //单点更新 只是用于区间求最值 区间添加和区间求最值是不能一起用的
int t = F[x];
a[x] = v;
for(int i = L[t]; i <= R[t]; i++)Max[t] = max(Max[t],a[i]);
}
inline int Quarymax(int x,int y){ //求区间最大值
int ans = -1;
if(F[x]==F[y]){ //如果区间被包含于一个整块
for(int i = x; i <= y; i++)ans = max(ans,a[i]);
return ans;
}
//如果区间跨过整个块
for(int i = x; i <= R[F[x]]; i++)ans = max(ans,a[i]);
for(int i = L[F[y]]; i <= y; i++)ans = max(ans,a[i]);
for(int i = F[x]+1; i < F[y]; i++)ans = max(ans,Max[i]);
return ans;
}
int main(){
cin >> n;
Build(); //以下用来测试
int aa,bb;
int t = 10;
while(t--){
cin >> aa >> bb;
cout << Quarymax(aa,bb) << endl;
cin >> aa >> bb;
update(aa,bb);
}
return 0;
}
2.莫队算法
莫队算法是一种可以解决大部分区间离线问题的离线算法
其主要思想是分块
所以呢,时间复杂度O(n n \sqrt{n} n),也不失为一个解决问题的好办法。
莫队算法主要是基于分块算法,所以学习莫队算法之前要现了解分块算法。
光有分块还是不够的还需要进行对访问区间进行从小到大排序。
莫队算法其他部分很好写,最主要就是Add()(注:贡献加法函数)和Sub()(注:贡献减法函数)两个函数难写,因为这两个函数会根据不同的题目不同而写法不同。
建议结合题目和代码来深刻理解莫队这个算法
1. 下面给出洛谷的一道经典的利用莫队算法的例题 题目链接
题意: 题目很好理解,就是求给定区间中元素出现次数平方和。
贴上代码:
#include <bits/stdc++.h>
using namespace std;
#define JS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
typedef long long ll;
const int maxn = 5e4+10;
int a[maxn],pos[maxn],cnt[maxn];
ll ans[maxn],res;
struct Q{
int l,r,k;
}q[maxn];
bool cmp(Q x,Q y){return pos[x.l]==pos[y.l]?x.r<y.r:pos[x.l]<pos[y.l];}
void Add(int n){cnt[a[n]]++;res+=cnt[a[n]]*cnt[a[n]]-(cnt[a[n]]-1)*(cnt[a[n]]-1);}//用更新后的平方减去之前的平方
void Sub(int n){cnt[a[n]]--;res-=(cnt[a[n]]+1)*(cnt[a[n]]+1)-cnt[a[n]]*cnt[a[n]];}//用更新前的平方减去之后平方
int main(){
JS
int n,m,k;
cin >> n >> m >> k;
int le = sqrt(n); //分块
if(n%le)le++;
for(int i = 1; i <= n; i++)cin >> a[i],pos[i]=i/le;
for(int i = 0; i < m; i++){
cin >> q[i].l >> q[i].r;
q[i].k = i; //记录这个询问的次序
}
sort(q,q+m,cmp); //排序
int l = 1,r = 0;
for(int i = 0; i < m; i++){
while(q[i].l < l) Add(--l); //当前答案res的l大于询问的l
while(q[i].r > r) Add(++r); //当前答案res的r小于询问的r
while(q[i].l > l) Sub(l++); //当前答案res的l小于询问的l
while(q[i].r < r) Sub(r--); //当前答案res的r大于询问的r
ans[q[i].k] = res; //记录答案
}
for(int i = 0; i < m; i++)cout << ans[i] << endl;
return 0;
}
2. 有能力可以试着做一做牛课上一道有关莫队算法 题目链接
代码也贴上一下
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
const ll N=50010;
const ll M=100010;
const ll S=1000;
ll n,m,a[N],cnt[M],nr,nl,now,ans[M],pos[N];
set<ll> s;
struct Q{
ll l,r,k,id;
}q[N];
bool cmp(Q x,Q y){return pos[x.l]==pos[y.l]?x.r<y.r:pos[x.l]<pos[y.l];}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
//这两个函数Add()和Sub()这次的作用就是缩短计数的次数
void Add(ll x,ll y){++cnt[x];}
void Sub(ll x,ll y){--cnt[x];}
void move_to(ll tl,ll tr,ll tt){
while(nr<tr)Add(a[++nr],tt);
while(nl>tl)Add(a[--nl],tt);
while(nr>tr)Sub(a[nr--],tt);
while(nl<tl)Sub(a[nl++],tt);
for(set<ll>::iterator it=s.begin();it!=s.end();it++){ //再次优化,用set去重
if(cnt[*it]!=0&&gcd(cnt[*it],tt)==1){
now++;
}
}//记录结果
}//区间维护
int main(){
cin>>n>>m;
int le = sqrt(n);
if(n%le)le++;
for(ll i=1;i<=n;i++){
cin>>a[i];
pos[i]=i/le;
s.insert(a[i]);
}
for(ll i=1;i<=m;i++){
scanf("%lld%lld%lld",&q[i].l,&q[i].r,&q[i].id);
q[i].k = i;
}
sort(q+1,q+m+1,cmp);
for(ll i=1;i<=m;i++){
move_to(q[i].l,q[i].r,q[i].id);
ans[q[i].k]=now;
now=0;
}
for(ll i=1;i<m;i++){
cout<<ans[i]<<endl;
}
cout<<ans[m];//输出结果
return 0;
}