BZOJ2038 [2009国家集训队]小Z的袜子(分块法)

【题解】

本题中,每个区间的答案不能由子问题合并得到,所以不能使用分治一类的数据结构如线段树等 
而 ans = sigma( cnt[color[i]]*(cnt[color[i]]-1)/2 ),构成答案的颜色不止一种,所以要将不同颜色分开算答案 
但 n<=50000,对于每个询问,仅枚举颜色就超时了,只能从位置的角度考虑 

我这个蒟蒻只能写分块法。
分块的对象:由于区间的答案不能由子问题合并得到,所以对序列分块无效,考虑对询问分块 

考虑将询问按L排序并固定左端点,枚举右端点,记录途经颜色个数的暴力:对于每个L,仅L位置当然是O(1)的,枚举R位置是O(n)的 
它的时间浪费在:对同一位置的重复枚举 
其实这个暴力与分块是有联系的:
它相当于将询问按左端点分块,块的大小size=1,每个块内可以将R从小到大排序,O(n)顺推过去求答案 
那么,考虑分块的一般情况:块的大小为size,则:
同一块内的每个L,都要用size时间枚举答案,而R只需整体O(n)顺推,增减答案。总时间:枚举L:n*size,枚举R:n/size*n
将size取为sqrt(n),则程序总时间复杂度为:O( n*sqrt(n) ) 


【代码】

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
typedef long long LL;
LL ans[50005];
int col[50005],bel[50005],L[50005],R[50005],id[50005],cnt[50005];
void jh(int* a,int* b)
{
	int t=*a;
	*a=*b;
	*b=t;
}
void kp_L(int low,int high)
{
	int i=low,j=high,mid=L[(i+j)/2];
	while(i<j)
	{
		while(L[i]<mid) i++;
		while(L[j]>mid) j--;
		if(i<=j)
		{
			jh(&L[i],&L[j]);
			jh(&R[i],&R[j]);
			jh(&id[i],&id[j]);
			i++;
			j--;
		}
	}
	if(j>low) kp_L(low,j);
	if(i<high) kp_L(i,high);
}
void kp_R(int low,int high)
{
	int i=low,j=high,mid=R[(i+j)/2];
	while(i<j)
	{
		while(R[i]<mid) i++;
		while(R[j]>mid) j--;
		if(i<=j)
		{
			jh(&L[i],&L[j]);
			jh(&R[i],&R[j]);
			jh(&id[i],&id[j]);
			i++;
			j--;
		}
	}
	if(j>low) kp_R(low,j);
	if(i<high) kp_R(i,high);
}
void kp_id(int low,int high)
{
	int i=low,j=high,mid=id[(i+j)/2];
	while(i<j)
	{
		while(id[i]<mid) i++;
		while(id[j]>mid) j--;
		if(i<=j)
		{
			jh(&L[i],&L[j]);
			jh(&R[i],&R[j]);
			jh(&id[i],&id[j]);
			i++;
			j--;
		}
	}
	if(j>low) kp_id(low,j);
	if(i<high) kp_id(i,high);
}
LL C(int x)
{
	return (LL)x*((LL)x-1LL)/2LL;
}
LL gcd(LL a,LL b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}
int main()
{
	LL t,g;
	int n,m,i,size,from=1,to,l,r;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%d",&col[i]);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&L[i],&R[i]);
		id[i]=i;
	}
	kp_L(1,m);
	size=(int)sqrt(n);
	for(i=1;i<=n;i++)
		bel[i]=(i-1)/size+1;
	for(to=1;to<=m;to++)
		if(bel[L[to+1]]!=bel[L[from]])//内部允许O(n)的算法 
		{
			memset(cnt,0,sizeof(cnt));
			kp_R(from,to);
			l=L[from]+1;
			r=L[from];
			for(i=from;i<=to;i++)
			{
				if(i>from) ans[id[i]]=ans[id[i-1]];
				while(r<R[i])
				{
					//printf("r=%d ",r);
					ans[id[i]]-=C(cnt[col[++r]]);
					ans[id[i]]+=C(++cnt[col[r]]);
				}
				while(l<L[i])
				{
					ans[id[i]]-=C(cnt[col[l]]--);
					ans[id[i]]+=C(cnt[col[l++]]);
				}
				while(l>L[i])
				{
					ans[id[i]]-=C(cnt[col[--l]]);
					ans[id[i]]+=C(++cnt[col[l]]);
				}
			}
			from=to+1;
		}
	kp_id(1,m);
	for(i=1;i<=m;i++)
	{
		if(ans[i]==0) printf("0/1\n");
		else
		{
			t=C(R[i]-L[i]+1);
			g=gcd(ans[i],t);
			printf("%lld/%lld\n",ans[i]/g,t/g);
		}
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值