莫隊演算法模板以及簡單的入門題總結
莫隊模板
//莫隊演算法主要處理離線問題,查詢只給出L,R
//當[L,R]很容易向[L-1,R],[L+1,R],[L,R-1],[L,R+1]轉移時可用莫隊
//注意轉移的時候先擴張再收縮,L先向右,L再向左,最後再收縮
//add就是當前區間新增某元素時要做的操作
//del就是當前區間刪除某元素時要做的操作
//add,del函式寫的時候都要注意結構順序
struct node
{
int l,r,id;
}Q[maxn];
int pos[maxn];//儲存所在塊
bool cmp(const node &a,const node &b)
{
if(pos[a.l]==pos[b.l])
return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int a[maxn];
ll ans[maxn];//儲存每個查詢得答案
int L=0,R=0;//多組記得重置
ll Ans=0;//多組記得重置
void add(int x);
void del(int x);
int main()
{
scanf("%d%d%d",&n,&m,&k);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=a[i]^a[i-1 ];
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L+1>Q[i].l)
{
L--;
add(L);
}
while (L+1<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
莫隊演算法第一題
http://codeforces.com/contest/617/problem/E
題意就是給出n個數的序列和數字k,q次詢問,每次詢問給出[L,R],求這個區間內有多少個連續區間的異或和等於k。
由於我們知道
,
所以我們只要把原陣列轉為字首異或和陣列,問題就變為給定區間內有多少對i,j滿足A[i]^A[j]=k.,所以add,del函式的寫法就很明顯了。
莫隊第一題程式碼
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long ll;
const int maxn = 2e6+5;
struct node
{
int l,r,id;
}Q[maxn];
int pos[maxn];
bool cmp(const node &a,const node &b)
{
if(pos[a.l]==pos[b.l])
return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int n,m,k;
int flag[maxn];//數字在當前區間出現次數
int a[maxn];
ll ans[maxn];
int L=0,R=0;
ll Ans=0;
void add(int x)
{
Ans+=flag[a[x]^k];//a^b=c那麼a^c=b
flag[a[x]]++;//先統計答案再加,以免多算一次當前的x
}
void del(int x)
{
flag[a[x]]--;//先減再統計答案,以免多減當前的x
Ans-=flag[a[x]^k];//a^b=c那麼a^c=b
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=a[i]^a[i-1];//陣列轉為字首異或和陣列
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
flag[0]=1;//沒有數字的時後,表示字首異或和為0
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L+1>Q[i].l)
{
L--;
add(L);
}
while(L+1<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
莫隊演算法第二題
BZOJ2038
題意就是給你一個n個數字的數字序列,Q次查詢操作給一個[L,R],求從這個區間任取兩個數而且這兩個數相等的概率
我們可以統計這個區間每種數的個數sum[x],數字x對答案的貢獻為x*(x-1),最後除以整個區間的方案數len*(len-1)就可以了,所以add函式的時候,一個新的x對答案的貢獻就是加入x之前的sum[x],del函式的時候,減少的應該是刪除x之後的sum[x],這樣就結束了。
莫隊第二題程式碼
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e5+5;
struct data
{
int l,r,id;
}Q[maxn];
int pos[maxn];
int a[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
long long ans[maxn];
long long ans2[maxn];
int flag[maxn];
int L=1,R=0;//由於第一個刪除0的操作對答案有影響,所以可以直接L=1開始。
long long Ans=0;
long long gcd_(long long a,long long b)
{
return b==0?a:gcd_(b,a%b);
}
void add(int x)//先統計再加
{
Ans+=flag[a[x]];
flag[a[x]]++;
}
void del(int x)//先減再統計
{
flag[a[x]]--;
Ans-=flag[a[x]];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
ans2[Q[i].id]=(1LL*(Q[i].r-Q[i].l+1)*(Q[i].r-Q[i].l))/2;
long long tmp = gcd_(ans[Q[i].id],ans2[Q[i].id]);
ans[Q[i].id]/=tmp;
ans2[Q[i].id]/=tmp;
if(ans[Q[i].id]==0) ans2[Q[i].id]=1;
}
for(int i=1;i<=m;i++)
printf("%lld/%lld\n",ans[i],ans2[i]);
return 0;
}
莫隊演算法第三題
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e6+5;
struct data
{
int l,r,id;
}Q[maxn];
long long ans[maxn];
int pos[maxn];
int a[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int flag[maxn];
int L=1,R=0;
long long Ans=0;
void add(int x)
{
flag[a[x]]++;
if(flag[a[x]]==1) Ans++;
}
void del(int x)
{
flag[a[x]]--;
if(flag[a[x]]==0) Ans--;
}
int main()
{
int n,m;
scanf("%d",&n);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
莫隊演算法第四題
http://codeforces.com/problemset/problem/86/D
題意就是每種數字x對答案的貢獻是(x*x*出現次數),所以add函式和del函式就很明顯了。
但是由於讀入比較多,需要掛個簡單的讀入掛
莫隊第四題程式碼
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
int a[maxn];
long long sum[maxn];
int pos[maxn];
long long ans[maxn];
struct data
{
int l,r,id;
}Q[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L=0,R=0;
long long Ans=0;
void add(int x)
{
sum[a[x]]++;
Ans+=(1LL*(2LL*sum[a[x]]-1)*a[x]);//算出簡單的轉移減少常數
}
void del(int x)
{
sum[a[x]]--;
Ans-=(1LL*(2LL*sum[a[x]]+1)*a[x]);
}
int read()
{
ll x=0;
char c=getchar();
while(c < '0' || c > '9')
{
c = getchar();
}
while(c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return x;
}
int main()
{
int n,t;
scanf("%d%d",&n,&t);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
a[i]=read();
pos[i]=i/sz;
}
for(int i=1;i<=t;i++)
{
Q[i].l=read();
Q[i].r=read();
Q[i].id=i;
}
sort(Q+1,Q+1+t,cmp);
for(int i=1;i<=t;i++)
{
while(L>Q[i].l)
{
L--;
add(L);
}
while(R<Q[i].r)
{
R++;
add(R);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=t;i++) printf("%I64d\n",ans[i]);
return 0;
}
莫隊演算法第五題
HDU5213
給你一個n個數的序列a,給你q個詢問,每次詢問給兩個不相交的區間,求a[i]+a[j]=k的方案數
i屬於第一個區間,j屬於第二個區間。
對於這種兩個區間內查詢得問題,我們要看看能不能轉化為一個區間之內的查詢操作。
我們設為區間內選i,j,的方案數。若兩個區間為
則答案為
所以我們對這四個部分分別處理就可以了。這就是多區間詢問轉為莫隊演算法的一種套路。
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int a[maxn];
int pos[maxn];
ll sum[maxn];
ll ans[maxn];
int n,m,k;
struct data
{
int l,r,id,belong;//belong是這個查詢屬於哪個查詢,id是這個查詢對答案貢獻是+還是-。
}Q[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L=0,R=0;
ll Ans=0;
void add(int x)
{
if(k>a[x]&&k-a[x]<n) Ans+=sum[k-a[x]];
sum[a[x]]++;
}
void del(int x)
{
sum[a[x]]--;
if(k>a[x]&&k-a[x]<n) Ans-=sum[k-a[x]];
}
int main()
{
while(scanf("%d%d",&n,&k)!=EOF)
{
L=0,R=0,Ans=0;
for(int i=1;i<=n;i++) sum[i]=0;
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
scanf("%d",&m);
for(int i=1;i<=m;i++) ans[i]=0;
int cnt=1;
for(int i=1;i<=m;i++)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
Q[cnt].l=l1,Q[cnt].r=r2,Q[cnt].id=1,Q[cnt++].belong=i;
Q[cnt].l=l1,Q[cnt].r=l2-1,Q[cnt].id=-1,Q[cnt++].belong=i;
Q[cnt].l=r1+1,Q[cnt].r=r2,Q[cnt].id=-1,Q[cnt++].belong=i;
Q[cnt].l=r1+1,Q[cnt].r=l2-1,Q[cnt].id=1,Q[cnt++].belong=i;
}
cnt--;
sort(Q+1,Q+1+cnt,cmp);
for(int i=1;i<=cnt;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].belong]+=1LL*Ans*Q[i].id;
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}
return 0;
}
莫隊演算法第六題
Problem 2226 信心題
給定一個含有n個數字的數列,每個數字都有一個值a[i](下標從1開始)。定義第i個數字和第j個數字間的距離dis(i,j)=abs(i-j)。
資料範圍:
N<=10^5
Q<=10^4
1<=a[i]<=10^3
1<=l<=r<=n
注意到a[i]小於1000,所以我們如果我們可以得到每種a[i]最左出現位置和最優出現位置,然後1000個a[i]掃一遍就可以
所以我們莫隊維護每種數字在原序列中的最左下標和最右下標就可以了,所以add函式和del函式的寫法就很簡單了。
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5+5;
int a[maxn];
int cnt1[maxn];//存左下標
int cnt2[maxn];//存右下標
int pos[maxn];
int ans[maxn];
vector<int> v[1005];
struct data
{
int l,r,id;
}Q[10005];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L,R,Ans;
int main()
{
int n,m;
while(scanf("%d",&n)!=EOF)
{
L=0,R=0,Ans=0;
for(int i=1;i<=n;i++)
{
v[i].clear();
cnt1[i]=0;
cnt2[i]=-1;//這裡的初始化與莫隊有關,需要思考一下,
}
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
pos[i]=i/sz;
scanf("%d",&a[i]);
v[a[i]].push_back(i);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
cnt2[a[R]]++;//區間向右擴,當前數的最右下標右移
}
while(L>Q[i].l)
{
L--;
cnt1[a[L]]--;//區間向左擴,當前數的最左下標左移
}
while(L<Q[i].l)
{
cnt1[a[L]]++;//區間向右壓,當前數的最左下標右移
L++;
}
while(R>Q[i].r)
{
cnt2[a[R]]--;//區間向左壓,當前數的最右下標左移
R--;
}
Ans=0;
for(int j=1;j<=1000;j++)
{
if(cnt2[j]<0) continue;
int tmp1=v[j][cnt2[j]];
int tmp2=v[j][cnt1[j]];
Ans=max(Ans,tmp1-tmp2);
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
return 0;
}
莫隊演算法第七題
HDU4638
題意就是給你一個長度為n的序列,每次查詢給一個區間,問區間可被分成多少個連續的段
例如 1,2,4,5被分為 [1,2],[4,5]兩段。
我們可以考慮新增一個數對答案的影響,新增一個數的時候,如果他左右的數字都存在,顯然段數-1
若左右存在某一個,段數不變
若左右均不存在,則段數+1
刪除一個數的時候類似,自己推導一下就可以了。
莫隊第七題程式碼
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
#define dbg(x) cout<<#x<<" = "<<x<<endl;
const int maxn = 1e5+5;
int a[maxn];
int pos[maxn];
int ans[maxn];
int vis[maxn];
struct data
{
int l,r,id;
}Q[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L,R,Ans;
void add(int i)
{
int id=a[i];
Ans-=(vis[id-1]+vis[id+1]-1);
vis[id]=1;
}
void del(int i)
{
int id=a[i];
Ans+=(vis[id-1]+vis[id+1]-1);
vis[id]=0;
}
int main()
{
int n,m;
int T;
scanf("%d",&T);
while(T--)
{
L=1,R=0,Ans=0;
scanf("%d%d",&n,&m);
for(int i=0;i<=2*n;i++)
{
vis[i]=0;
}
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);