莫隊算法

莫隊算法常用於處理不帶修改的連續區間的詢問問題上,一http://般來說都有以下這種形式:


給你n(1e5左右)個數字A[i],再給你q(1e5左右)個詢問,每個詢問包含一個l和r,表示用某種操作,對原數組的A[l...r]進行操作,並求出值。


一般來說,用莫隊算法的情況下,我們已知A[l...r]的值,要轉移到A[l'...r']的值需要花費爲O((|l - l'| + |r - r'|) * T)的話,那麼總的時間複雜度就爲O((n + q) * sqrt(n) * T).這裏的證明不再說明,具體可見最小曼哈頓生成樹。


莫隊算法一定是離線算法,其中先保存下來所有的詢問。對於每個詢問排序,設S爲sqrt(n),詢問要按照如下排序,

bool cmp(query a,query b){
	return (a.l / S != b.l / S) ? a.l / S < b.l / S : a.r < b.r;
}

之後對於每一個詢問,直接暴力轉移即可。


例題1:

CF 86D

題意:

有一個數組A有n個數字,t個詢問,對於每個詢問有個l,r,計算A[l...r]中所有數字乘以它出現次數的平方的和。

解法就強行搬上面的結論就可以了

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 200005;

ll mark[maxn * 5];
int n,t;
ll now;
int blo;
struct ppp{
	int l,r,id;
	void read(int _id){
		scanf("%d%d",&l,&r);
		id = _id;
	}
	bool operator < (const ppp & nex)const{
		return (l / blo != nex.l / blo) ? l / blo < nex.l / blo : r > nex.r;
	}
}que[maxn];

void init(){
	blo = sqrt(n);
	now = 0;
}
int ori[maxn];
void add(int x){
	now += (2 * mark[x] + 1) * x;
	mark[x]++;
}
void del(int x){
	now += (-2 * mark[x] + 1) * x;
	mark[x]--;
}
ll ans[maxn];


int main(){
	while(cin>>n>>t){
		init();
		for(int i = 1;i <= n;i++)scanf("%d",ori + i);
		for(int i = 0;i < t;i++)que[i].read(i);
		sort(que,que + t);
		int l = 1,r = 0;
		for(int i = 0;i < t;i++){
			while(que[i].l < l)add(ori[--l]);
			while(que[i].l > l)del(ori[l++]);
			while(que[i].r < r)del(ori[r--]);
			while(que[i].r > r)add(ori[++r]);
			ans[que[i].id] = now;
		}
		for(int i = 0;i < t;i++)printf("%I64d\n",ans[i]);
	}
}


例題2:

CF 617E

題意:

給你一個數組A有n個數字,給你m個詢問,每個詢問l,r,要你求出A[l...r]中有多少不同組i,j(l <= i <= j <= r)滿足A[i] xor A[i + 1] xor ...A[j]的值爲k,輸出組數。

題解:

那麼我們只要保存A[1]到A[i](for all i)的異或和表示爲func[i],那麼A[i] xor ...A[j]就是func[i - 1] xor func[j],那麼就可以寫了。要注意的是當前數組在往左邊移動時,要計算到A[i - 1]爲止,往右邊移動時到A[j]就好,這樣才滿足func[i - 1] xor func[j]。

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 100005;

int n,m,k;
struct ppp{
	int l,r,id;
	void read(int _id){
		scanf("%d%d",&l,&r);
		id = _id;
	}
}que[maxn];
int ori[maxn];
int xor_sum[maxn];
int sqrt_n;
ll ans[maxn];
ll cnt[1 << 20];
bool cmp(ppp a,ppp b){
	return (a.l / sqrt_n != b.l / sqrt_n) ? a.l / sqrt_n < b.l / sqrt_n : a.r > b.r;
}

void show(){
	for(int i= 1;i <= n;i++)cout<<xor_sum[i ^ ori[i]]<<" ";
	cout<<endl;
	for(int i = 1;i <= n;i++)cout<<xor_sum[i]<<" ";
	cout<<endl;
}

ll anss;
void add(int x){
	anss += cnt[x ^ k];
	cnt[x]++;
}
void del(int x){
	cnt[x]--;
	anss -= cnt[x ^ k];
}

int main(){
	while(cin>>n>>m>>k){
		for(int i = 1;i <= n;i++)scanf("%d",&ori[i]);
		for(int i = 0;i < m;i++)que[i].read(i);
		mem(cnt,0);
		xor_sum[0] = 0;
		anss = 0;
		for(int i = 1;i <= n;i++)xor_sum[i] = xor_sum[i - 1] ^ ori[i];
		sqrt_n = sqrt(n);
		sort(que,que + m,cmp);
		int prel = 1,prer = 0;
		for(int i = 0;i < m;i++){
			while(que[i].l - 1 > prel)del(xor_sum[prel++]);//左邊都是維護l - 1,右邊維護r
			while(que[i].l - 1 < prel)add(xor_sum[--prel]);//那麼異或的結果就是l ~ r之間的
			while(que[i].r > prer)add(xor_sum[++prer]);
			while(que[i].r < prer)del(xor_sum[prer--]);
			int &id = que[i].id;
			ans[id] = anss;
		} 
		for(int i = 0;i < m;i++)printf("%I64d\n",ans[i]);
	}
}








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章