2038: [2009国家集训队]小Z的袜子(hose)

听说 这个算法叫莫队。
若一个区间[l,r]所有颜色的袜子总数cnt[i]已预处理好,我们可以直接求出答案:∑(cnt[i]-1)*cnt[i]/((r-l+1)*(r-l))
经观察,我们知道区间[l-1,r]、[l+1,r]、[l,r-1]、[l,r+1]均可O(1)求得。
考虑分块,将所有坐标分成sqrt(n)个区间,然后对询问离线后进行排序,第一关键字为左端点所在区间编号,第二关键字为右端点大小。
然后从左往右暴力统计就可以了,感性理解可知,时间复杂度为O(nsqrt(n))。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<cmath>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define ll long long
#define db double
#define ldb long double
#define pll pair<ll,ll>
#define mkp make_pair
#define X first
#define Y second
const int N=50005;
struct QUERY{ll L,R,flg;}qu[N];
ll n,m,s,a[N],md[N];ll cnt[N];pll ans[N];
bool cmp(QUERY x,QUERY y){
	return md[x.L]==md[y.L]?x.R<y.R:x.L<y.L;
}
ll gcd(ll x,ll y){
	ll z=x%y;
	return z?gcd(y,z):y;
}
int main(){
	ll i,j,x,y,u,v;ll S;
	scanf("%lld%lld",&n,&m);
	s=(int)sqrt(n);x=1;
	rep(i,1,n){
		scanf("%lld",&a[i]);
		md[i]=x;
		x+=i%s==0;
	}
	rep(i,1,m){
		scanf("%lld%lld",&x,&y);
		qu[i]=(QUERY){x,y,i};
	}
	sort(qu+1,qu+m+1,cmp);
	x=qu[1].L,y=qu[1].R;
	rep(i,x,y)++cnt[a[i]];
	S=0;
	rep(i,1,n)S+=cnt[i]*(cnt[i]-1);
	ans[qu[1].flg]=mkp(S,(y-x+1)*(y-x));
	rep(i,2,m){
		u=qu[i].L,v=qu[i].R;
		if(v>y)rep(j,y+1,v)S+=cnt[a[j]]++<<1;
		else per(j,y,v+1)S-=--cnt[a[j]]<<1;
		if(u<x)per(j,x-1,u)S+=cnt[a[j]]++<<1;
		else rep(j,x,u-1)S-=--cnt[a[j]]<<1;
		x=u,y=v;
		ans[qu[i].flg]=mkp(S,(y-x+1)*(y-x));
	}
	rep(i,1,m){
		if(!ans[i].X)puts("0/1");
		else{
			x=gcd(ans[i].X,ans[i].Y);
			printf("%lld/%lld\n",ans[i].X/x,ans[i].Y/x);
		}
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值