概述
借鉴:大佬1、大佬2
通过对离线问题的处理是莫队算法的核心
数据结构简化操作一:优化
通过对a[L-1]和a[R+1]的值对答案的影响的处理,来扩大区间
如果[L,R]要变成[l,r],需要判断|l-L|+|r-R|,也就是曼哈顿距离。
数据结构简化操作二:分块。
分成根号n个块,把所有询问按左端点放进各自的块内,对于每个块按右端点排序。
考虑总体复杂度:
对于每个块内右端点的移动,是n级别的移动,一共有根号n个块。复杂度是n根号n;
左端点的移动是M次询问*根号n【也有可能是n根号n,如果只有两次询问但跨越了比较多】
总体加起来=n根号n
小Z的袜子【模板】
题意:对多个询问,询问区间里选出两个数,两个数相同的概率。
题解:
P=C(A1,2)+C(A2,2)+C(A3,3)+...+C(An,n)/C(n,2)
=[A1(A1-1)/2+A2(A2-1)+...An(An-1)/2]/[n*(n-1)/2]
=[A1^2+A2^2+...+An^2-n]/[n*(n-1)/2]
维护区间内数量和即可。
代码如下:
#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
int n,m;
int A[50005];
int M[50005];
int pos[50005];
long long mid=0,L=1,R=1;
void add(int x){
mid-=M[x]*M[x];
M[x]++;
mid+=M[x]*M[x];
}
void del(int x){
mid-=M[x]*M[x];
M[x]--;
mid+=M[x]*M[x];
}
struct node{
long long l,r;
int index;
void inq(){scanf("%lld%lld",&l,&r);}
friend bool operator < (node a,node b){
if(pos[a.l]!=pos[b.l])return a.l<b.l;
else return a.r<b.r;
}
}Q[50005],C[50005];
int main(){
cin>>n>>m;
FOR(i,1,n)scanf("%d",&A[i]);
int block=sqrt(n);
FOR(i,1,n){
pos[i]=i/block;
}
FOR(i,1,m){
Q[i].inq();
Q[i].index=i;
}
sort(Q+1,Q+1+m);
mid=1;
M[A[1]]++;
FOR(i,1,m){
while(L>Q[i].l){
L--;
add(A[L]);
}
while(L<Q[i].l){
del(A[L]);
L++;
}
while(R<Q[i].r){
R++;
add(A[R]);
}
while(R>Q[i].r){
del(A[R]);
R--;
}
long long li=mid-(Q[i].r-Q[i].l+1);
long long lm=(Q[i].r-Q[i].l+1)*(Q[i].r-Q[i].l);
if(li==0)C[Q[i].index].l=0,C[Q[i].index].r=1;
else{
int gcd=__gcd(li,lm);
C[Q[i].index].l=li/gcd,C[Q[i].index].r=lm/gcd;
}
}
FOR(i,1,m){
cout<<C[i].l<<"/"<<C[i].r<<endl;
}
}
可以看出来:代码的核心在于分块。
1_处理排序中对块的判断。
2_考虑到左右区间的移动。
训练:
Powerful array
题意:维护区间询问:种类数量和平方*自身种类的和。
题解:同小Z的袜子。主要维护数量的数组,每次add和del函数进行更新。
#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++)
#define LL long long
using namespace std;
const int maxn = 200050;
int A[maxn];
int pos[maxn];
int M[1000050];
int n,m;
long long mid=0;
long long B[maxn];
void init(){
int block=sqrt(n);
FOR(i,1,n){
pos[i]=i/block;
}
}
void add(int x){
LL st=M[x];
mid-=st*st*(LL)x;
M[x]++;
mid+=(st+1)*(st+1)*(LL)x;
}
void del(int x){
LL st=M[x];
mid-=st*st*(LL)x;
M[x]--;
mid+=(st-1)*(st-1)*(LL)x;
}
struct node{
int l,r;
int index;
void inp(int now){scanf("%d%d",&l,&r);index=now;}
friend bool operator < (node a,node b){
if(pos[a.l]!=pos[b.l])return a.l<b.l;
else return a.r<b.r;
}
}Q[maxn];
int main(){
cin>>n>>m;
FOR(i,1,n)scanf("%d",&A[i]);
init();
FOR(i,1,m)Q[i].inp(i);
sort(Q+1,Q+1+m);
int L=1,R=1;
M[A[1]]++;
mid=1*1*A[1];
FOR(i,1,m){
while(L<Q[i].l){
del(A[L]);
L++;
}
while(L>Q[i].l){
L--;
add(A[L]);
}
while(R<Q[i].r){
R++;
add(A[R]);
}
while(R>Q[i].r){
del(A[R]);
R--;
}
B[Q[i].index]=mid;
}
FOR(i,1,m){
cout<<B[i]<<endl;
}
}
有趣的结合题
题意:多个询问,对给定区间内,判断在[a,b]中的数有多少个,数的种类有多少个。
题解:要求两个答案其实异曲同工。
每次加数量的时候,判断是不是从0加到1,从而对种类修改。
对于给定区间,[a,b]的答案,我们可以用分块来求解:每个块的数有多少个和种类被记录后求解的复杂度只有根号n。
对于多个区间,我们需要维护每个块的数和种类,这个时候需要维护每个数出现的cnt,每次对cnt修改的时候,同时对每个块的记录进行修改。【显然莫队】
【莫队加分块:加快读快写可以快400ms,平均下来应该是200ms】
算是分块+莫队的各自模板题。
#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++)
#define LL long long
using namespace std;
int n,m;
const int maxn = 100010;
const int maxm = 200010;
int A[maxn],B[maxn],C[maxn];
int cnt[maxm],pos[maxm];
int all[maxm],num[maxm];
int block;
inline int read() {
int num=0, w=0;
char ch=0;
while (!isdigit(ch)) {
w|=ch=='-';
ch = getchar();
}
while (isdigit(ch)) {
num = (num<<3) + (num<<1) + (ch^48);
ch = getchar();
}
return w? -num: num;
}
inline void write(int x)
{
if(x<0) {
putchar('-');
x = -x;
}
if(x>9) write(x / 10);
putchar(x % 10 + '0');
}
void init(){
block=sqrt(n);
FOR(i,1,n)pos[i]=(i-1)/block+1;
}
void add(int x){
++cnt[x];
if(cnt[x]==1)++num[pos[x]];
++all[pos[x]];
}
void del(int x){
--cnt[x];
if(cnt[x]==0)--num[pos[x]];
--all[pos[x]];
}
struct node{
int l,r,a,b;
int index;
void inp(int ind){l=read();r=read();a=read();b=read();index=ind;}
friend bool operator < (node a,node b){
if(pos[a.l]!=pos[b.l])return pos[a.l]<pos[b.l];
else return a.r<b.r;
}
}Q[maxn];
void cal(int u){
int start=Q[u].a,end=Q[u].b,ind=Q[u].index;
if(pos[start]==pos[end]){
for(int i=start;i<=end;i++){
B[ind]+=cnt[i];
if(cnt[i])C[ind]++;
}
}
else{
for(int i=start;i<=pos[start]*block;i++){B[ind]+=cnt[i];if(cnt[i])C[ind]++;}
for(int i=pos[start]+1;i<pos[end];i++){B[ind]+=all[i];C[ind]+=num[i];}
for(int i=(pos[end]-1)*block+1;i<=end;i++){B[ind]+=cnt[i];if(cnt[i])C[ind]++;}
}
}
int main(){
cin>>n>>m;
init();
FOR(i,1,n)A[i]=read();
FOR(i,1,m){
Q[i].inp(i);
}
int L=1,R=0;
sort(Q+1,Q+1+m);
FOR(i,1,m){
while(L<Q[i].l){
del(A[L]);
++L;
};
while(L>Q[i].l){
--L;
add(A[L]);
};
while(R<Q[i].r){
++R;
add(A[R]);
};
while(R>Q[i].r){
del(A[R]);
--R;
};
cal(i);
}
FOR(i,1,m){
write(B[i]);
printf(" ");
write(C[i]);
printf("n");
}
}
这是对以前莫队的一次更新,算是复习。
如果还有基础莫队的好题,我会继续加上来。
如果准备学习进阶莫队了,我会单独再开一个版块。
最后
以上就是发嗲菠萝为你收集整理的莫队算法借鉴:大佬1、大佬2训练:的全部内容,希望文章能够帮你解决莫队算法借鉴:大佬1、大佬2训练:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复