P1494 [国家集训队] 小 Z 的袜子(分块,莫队算法)

题目链接

莫队算法讲解(OI wiki)

最近吃药吃的神智不是很清醒。


思路:

莫队算法就是将询问进行分块,整体对询问的左端点进行排序,块内对右端点进行排序。莫队算法的时间复杂度是 O ( n n ) O(n\sqrt n) O(nn ) 的,假设序列总长度为 n n n,询问次数为 m m m,分块大小为 S S S,那么就有 n S \dfrac n S Sn 个块,每个块中右端点是单调递增的,因此移动右端点次数不会超过 n n n n S \dfrac n S Sn 个块那么右端点一共移动不会超过 n 2 S \dfrac {n^2} S Sn2 次,在一个块内左端点的移动次数不会超过 S S S m m m 个询问那么左端点一共移动大概是 m ∗ S m*S mS 次。这样左右端点一共会移动 n 2 S + m ∗ S \dfrac {n^2} S+m*S Sn2+mS 次,根据均值不等式可以得到当 S = n m S=\dfrac n {\sqrt{m}} S=m n 时, n 2 S + m ∗ S = 2 n 2 S ∗ m ∗ S = 2 n m \dfrac {n^2} S+m*S= 2\sqrt{\dfrac {n^2} S*m*S}=2n\sqrt{m} Sn2+mS=2Sn2mS =2nm 最小。当 n = m n=m n=m 时,可以发现 S = n S=\sqrt{n} S=n 时,总的运算次数 = n n =n\sqrt{n} =nn 也就是莫队算法的时间复杂度。

另外移动区间的 4 4 4 w h i l e while while 循环的位置很关键,不能随意改变它们之间的位置关系。一般来说,先让区间扩大,再让区间缩小是正确的(比如先 − − l , + + r --l,++r l,++r,再 l + + , r − − l++,r-- l++,r),因为先缩小区间话,有可能导致中间的某些运算过程中,区间是 l > r l>r l>r 的,这时有可能会出现一些问题。

莫队有个优化方法,也就是奇偶化排序。引用一下 O I w i k i OI wiki OIwiki:奇偶化排序即对于属于奇数块的询问, r r r 按从小到大排序,对于属于偶数块的排序, r r r 从大到小排序,这样我们的 r r r 指针在处理完这个奇数块的问题后,将在返回的途中处理偶数块的问题,再向 n n n 移动处理下一个奇数块的问题,优化了 r r r 指针的移动次数,一般情况下,这种优化能让程序快 30 % 30\% 30% 左右。


这个题就是莫队算法的板子题了。可以先算出每个块的长度和块的个数,然后处理出每个块的左右端点 L [ i ] , R [ i ] L[i],R[i] L[i],R[i],再求出每个下标所属块 p o s [ i ] pos[i] pos[i]。算是分块的起手式。

然后将询问读入并排序,如果左端点所在块不同,就有限按左端点排序,否则按右端点排序,这里可以用奇偶化排序优化。

之后设定一个区间,我们之后对这个区间的左右端点进行移动并实时计算这个区间内每个颜色的个数 c n t [ i ] cnt[i] cnt[i],区间初始化 l = 1 , r = 0 l=1,r=0 l=1,r=0

最后输出的时候要求分子分母的最大公因数,但是注意分子分母有可能都为 0 0 0,需要特判并输出 0 / 1 0/1 0/1

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=5e5+5;
typedef pair<int,int> pii;
typedef long long ll;

int n,m;
int a[maxn];
struct query{
	ll l,r;
	ll id;
	ll ans;
}qry[maxn];

int L[maxn],R[maxn],pos[maxn];

ll l=1,r=0,cnt[maxn];
ll ans=0;
void add(int id){
	auto &x=cnt[a[id]];
	ans-=1ll*x*(x-1)/2;
	x++;
	ans+=1ll*x*(x-1)/2;
}
void del(int id){
	auto &x=cnt[a[id]];
	ans-=1ll*x*(x-1)/2;
	x--;
	ans+=1ll*x*(x-1)/2;
}

ll gcd(ll a,ll b){
//	if(a<b)swap(a,b);
	while(b)b^=a^=b^=a%=b;
	return a;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=m;i++){
		auto &[l,r,id,t]=qry[i];
		cin>>l>>r;
		id=i;
	}
	int t=sqrt(n);
	int len=t?n/t:n;
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*len+1;
		R[i]=i*len;
	}
	if(R[t]<n){
		L[t+1]=R[t]+1;
		R[++t]=n;
	}
	for(int i=1;i<=t;i++)
		for(int j=L[i];j<=R[i];j++)
			pos[j]=i;
	
	sort(qry+1,qry+m+1,[&](query a,query b){
		if(pos[a.l]==pos[b.l])return (pos[a.l]&1)?a.r<b.r:a.r>b.r;
		else return a.l<b.l;
	});
	
	for(int i=1;i<=m;i++){
		auto &[L,R,id,t]=qry[i];
		while(r<R){
			add(++r);
		}
		while(R<r){
			del(r--);
		}
		while(l<L){
			del(l++);
		}
		while(L<l){
			add(--l);
		}
		t=ans;
	}
	sort(qry+1,qry+m+1,[&](query a,query b){
		return a.id<b.id;
	});
	for(int i=1;i<=m;i++){
		auto [l,r,id,t]=qry[i];
		ll len=r-l+1,fm=len*(len-1)/2,d=gcd(fm,t);
		if(!fm || !t){
			printf("0/1\n");
			continue;
		}
//		printf("%d %d %d %d\n",l,r,id,t);
		printf("%lld/%lld\n",t/d,fm/d);
	}
	return 0;
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值