莫队 - 例题+练习

lyw姐姐放假回来给我们讲课
比zgs好

莫队基础

分块降时间复杂度
把区间之间的转化优化为求一个曼哈顿距离 ( ∣ l 1 − l 2 ∣ + ∣ r 1 − r 2 ∣ ) (|l_1-l_2|+|r_1-r_2|) (l1l2+r1r2)
再优化这个曼哈顿距离——利用分块
即以L所在的块为第一关键字,R第二关键字排序

莫队要求离线

HH的项链
code

//莫队过不去 
#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define re register
const int LEN=1e6+10;
int n,m,size;
int line[LEN];
struct node{
	int l,r,i,p,ans;
}quary[LEN];
int cnt[LEN];
int ans;

inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(ch>='0'&&ch<='9')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

inline bool comp(const node &a,const node &b){
	if(a.p!=b.p)return a.p<b.p;
	return a.r<b.r;
}

inline bool comp_for_back(const node &a,const node &b){
	return a.i<b.i;
}

inline void Write(const int &a){
    int x=a;
    if(x<0){
        putchar('-');
        x=-x;
    }
    static short buf[20];
    short tot=0;
    do{
        buf[tot++]=x%10;
        x/=10;
    }while(x);
    while(tot)putchar(buf[--tot]+'0');
}

int main(){
	n=in;
	size=(int)sqrt(n);
	for(re int i=1;i<=n;i++)line[i]=in;
	m=in;
	for(re int i=1;i<=m;i++){
		quary[i].l=in;
		quary[i].r=in;
		quary[i].i=i;
		quary[i].p=(quary[i].l-1)/size+1;
	}
	
	sort(quary+1,quary+m+1,comp);
	
	ans=0;
	int l=1,r=0;
	for(re int i=1;i<=m;i++){
		
		while(r<quary[i].r){
			r++;
			if(!cnt[line[r]])ans++;
			cnt[line[r]]++;
		}
		while(r>quary[i].r){
			cnt[line[r]]--;
			if(!cnt[line[r]])ans--;
			r--;
		}
		while(l<quary[i].l){
			cnt[line[l]]--;
			if(!cnt[line[l]])ans--;
			l++;
		}
		while(l>quary[i].l){
			l--;
			if(!cnt[line[l]])ans++;
			cnt[line[l]]++;
		}
		
		quary[i].ans=ans;
	}
	
	sort(quary+1,quary+m+1,comp_for_back);
	
	for(re int i=1;i<=m;i++)Write(quary[i].ans),putchar('\n');
	return 0;
}

别去络谷,会T(用线段树就行了,可惜我还没把它搞熟嘤嘤嘤~)

练习:BZOJ 2038 3781
(提高)3289 3809 4540

BZOJ2038

莫队需要特别注意的点在于移动l,r指针的时候各种量之间的关系:加减关系,包含关系等

#include<bits/stdc++.h>
#define in Read()
#define re register
#define int long long
inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(ch<='9'&&ch>='0')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=5e4+10;
int n,m,c[NNN],size;
int r,l,cnt[NNN];
struct fraction{
	int nume,deno;
};
struct node{
	int l,r,i,p;
	fraction ans;
}query[NNN];

inline int gcd(int x,int y){
	if(x%y==0)return y;
	return gcd(y,x%y);
}

inline fraction frac(int nume/*分子*/,int deno/*分母:输进来r-l+1*/){
	fraction ans;
	//特判
	if(!nume){
		ans.nume=0,ans.deno=1;
		return ans;
	}
	
	//把分母调对 
	if(deno%2)deno=(deno-1)/2*deno;
	else deno=(deno/2)*(deno-1);
	
	int div=gcd(nume,deno);
	nume=nume/div;
	deno=deno/div;
	ans.nume=nume;
	ans.deno=deno;
	return ans;
}

inline bool comp(const node &a,const node &b){
	if(a.p!=b.p)return a.p<b.p;
	return a.r<b.r;
}

inline bool back(const node &a,const node &b){
	return a.i<b.i;
}

signed main(){
	n=in,m=in;
	size=(int)sqrt(n);
	for(re int i=1;i<=n;++i)c[i]=in;
	for(re int i=1;i<=m;++i){
		query[i].l=in;
		query[i].r=in;
		query[i].i=i;
		query[i].p=(query[i].l-1)/size;
	}
	
	std::sort(query+1,query+m+1,comp);
	
	int ans=0;
	l=1,r=0;
	for(re int i=1;i<=m;++i){
		
		while(r<query[i].r){
			++r;
			ans+=cnt[c[r]];
			++cnt[c[r]];
		}
		while(r>query[i].r){
			--cnt[c[r]];
			ans-=cnt[c[r]];
			--r;
		}
		while(l>query[i].l){
			--l;
			ans+=cnt[c[l]];
			++cnt[c[l]];
		}
		while(l<query[i].l){
			--cnt[c[l]];
			ans-=cnt[c[l]];
			++l;
		}
		
		query[i].ans=frac(ans,query[i].r-query[i].l+1);
	}
	
	std::sort(query+1,query+m+1,back);
	
	for(re int i=1;i<=m;++i)
		printf("%lld/%lld\n",query[i].ans.nume,query[i].ans.deno);
	
	return 0;
}

BZOJ3781

(很明显BZOJ并不想让你做这道题,那么全能的络谷就可以出马了)
这道题非常尴尬
我又在它上面浪费了1小时
在调试的时候遇到了一个非常日龙的情况:
在这里插入图片描述
然后
在这里插入图片描述
大草

最后发现MLE了

code

#include<bits/stdc++.h>
#define in Read()
#define re register
#define int long long
inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(ch<='9'&&ch>='0')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=5e4+10;
int n,m,k;
int a[NNN],cnt[NNN];
int l=1,r;
int ans;
struct node{
	int l,r,i,p;
	int ans;
}query[NNN];

inline int sqare(int x){return x*x;}

inline void INIT(){
	n=in,m=in,k=in;
	int size=(int)std::sqrt(n);
	for(re int i=1;i<=n;++i)a[i]=in;
	for(re int i=1;i<=m;++i){
		query[i].l=in;
		query[i].r=in;
		query[i].i=i;
		query[i].p=(query[i].l-1)/size;
	}
}

inline bool comp(const node &a,const node &b){return (a.p!=b.p)?a.p<b.p:a.r<b.r;}
inline bool back(const node &a,const node &b){return a.i<b.i;}

inline void solve(){
	for(re int i=1;i<=m;++i){
		
		while(r<query[i].r){
			++r;
			ans+=2*cnt[a[r]]+1;
			++cnt[a[r]];
		}
		while(r>query[i].r){
			ans-=2*cnt[a[r]]-1;
			--cnt[a[r]];
			--r;
		}
		while(l<query[i].l){
			ans-=2*cnt[a[l]]-1;
			--cnt[a[l]];
			++l;
		}
		while(l>query[i].l){
			--l;
			ans+=2*cnt[a[l]]+1;
			++cnt[a[l]];
		}
		
		query[i].ans=ans;
		
	}
}

signed main(){
	INIT();
	std::sort(query+1,query+m+1,comp);
	solve();
	std::sort(query+1,query+m+1,back);
	for(re int i=1;i<=m;++i){
		printf("%lld\n",query[i].ans);
	}
	return 0;
}

BZOJ3289

莫队+树状数组求逆序对
(求逆序对:冒泡O(N2),归并O(NlogN+代码难giao),树状数组O(NlogN))

#include<bits/stdc++.h>
#define in Read()
#define re register
inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(ch<='9'&&ch>='0')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=5e4+10;
int a[NNN],block;
struct node{
	int l,r,i,ans,p;
	inline void get_query(int _i){
		l=in,r=in,i=_i,p=(l-1)/block;//细节 
	}
}query[NNN];
int tree[NNN];
int n,q;
int disc[NNN];

inline bool comp(const node &u,const node &v){
	return (u.p!=v.p)?u.p<v.p:u.r<v.r;
}

inline bool back(const node &u,const node &v){
	return u.i<v.i;
}

inline int lowbit(int i){
	return i&(-i);
}

inline void Add(int x,int pos){
	while(x<=n){
		tree[x]+=pos;
		x+=lowbit(x);
	}
}

inline int Query(int x){
	unsigned int sum=0;
	while(x>0){
		sum+=tree[x];
		x-=lowbit(x);
	}
	return sum;
}

inline void SOLVE(){
	int l=1,r=0,ans=0;
	std::sort(query+1,query+q+1,comp);
	for(re int i=1;i<=q;++i){
		while(r<query[i].r){
			++r;
			Add(a[r],1);
			ans+=r-l+1-Query(a[r]);
		}
		while(r>query[i].r){
			Add(a[r],-1);
			ans-=r-l-Query(a[r]);
			--r;
		}
		while(l<query[i].l){
			Add(a[l],-1);
			ans-=Query(a[l]-1);
			++l;
		}
		while(l>query[i].l){
			--l;
			Add(a[l],1);
			ans+=Query(a[l]-1);
		}
		query[i].ans=ans;
	}
	std::sort(query+1,query+q+1,back);
}

int main(){
	n=in;
	block=(int)std::sqrt(n);
	for(re int i=1;i<=n;++i)disc[i]=a[i]=in;
	std::sort(disc+1,disc+n+1);
	for(re int i=1;i<=n;++i)a[i]=std::lower_bound(disc+1,disc+n+1,a[i])-disc;
	q=in;
	for(re int i=1;i<=q;++i)query[i].get_query(i);
	SOLVE();
	for(re int i=1;i<=q;++i)printf("%d\n",query[i].ans);
	return 0;
}

解释一下移动左右指针的地方

树状数组,Query(x)返回的是x到0所有点的和
每个点为权值表示的话,返回的就是存在于数组的、小于x的数字个数
每次改变一个点,就要计算生成或消失的逆序对个数

对于一个按照下标依次+1来加入的序列它的逆序对个数N要这样算:

Add
ans+=i-Query(i)

把这个结论应用到本题上:
每移动一个指针,加入或者消除树状数组中该点,然后计算

  1. r++
    元素入树
    总共r-l+1个数,比a[r]小的有Query个(可以算自己:最后r-l+1加的那个1是可以把加进去的自己去掉的,后面l是另一种算法,没有算自己的,因为不好确定l是否在树状数组里面)
    新增了r-l+1-Query个逆序对
  2. r–
    元素出树
    总共r-l个数(r–把出树的去掉了),比a[r]小的有Query个
    减少了r-l-Query个逆序对
  3. l++
    元素出树
    总共r-l个数,比a[l]小的有Query(a[l]-1)个(前缀和思想,不要吧自己包含进去了)
    减少了Query(a[l]-1)个
  4. l–
    元素入树
    总共r-l个数,比a[l]小的有Query(a[l]-1)个(前缀和思想)
    增加了Query(a[l]-1)个

注意再加点的时候加的是文件大小(a数组)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值