普通莫队算法(BZOJ 2038)

BZOJ 2038

 

思路:

每次查询区间L,R内的两个相同颜色的袜子出现的概率,

就是区间L,R内任选两个袜子,所有情况的数量为len*(len-1)(len表示这个区间内的所有元素的个数),也就是分母

记录每个不同颜色出现的次数为num[i],然后统计区间[l,r]内的所包含所有的不同颜色出现的次数的平方和减去len,

就是这个区间内所有不同颜色的组合,也就是分子(可以推导一下,假设区间内的一个颜色的数量为n1,任选两这种颜色的次数为n1*(n1-1)次,然后统计这个区间内的所有不同颜色求和,就得到∑ni*ni-∑ni = ∑ni*ni-len。)

 

可以先求解一个区间[L,R]内的相同颜色的袜子出现的次数,

通过修改L,R,用这个次数去推导其他区间的相同颜色的袜子出现的次数。

eg:

(1)[L,R]->[L+1,R]

L前进了,区间缩小了,所以先减去L位置的颜色出现的次数的平方:

ans -= num[col[L]]^2.

,然后修改L位置的颜色出现的次数:

num[col[L]]--;

然后再给ans+新的L位置的颜色出现的次数的平方。

ans += num[col[L]]^2;

 

(2)区间[L,R]->[L-1,R];

L的范围扩大了,对L-1位子的颜色进行操作:

ans -= num[col[L-1]]^2.

,然后修改L-1位置的颜色出现的次数:

num[col[L-1]]++;

然后再给ans+新的L-1位置的颜色出现的次数的平方。

ans += num[col[L-1]]^2;

 

(3)区间[L,R]->[L,R-1];

就是右端点R进行操作进行-1操作,区间减小;

 

(4)区间[L,R]->[L,R+1];

对区间R+1进行操作进行+1操作,区间扩大;

 

然后每次更新求出分子A,分母B。

但是直接用以上方法求解的话复杂度大,所以可以将所有查询的区间按照递增的方式排序,

对n个数字的位置划分为unit个区域,然后分别按照每个查询的左端点,右端点所属的区间进行排序,原因见这篇文章

unit = sqrt(n)。

所以最终的复杂度为O(n*sqrt(n))。

 

注意:结果要用longlong。

 

代码:

代码参考文章

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 1e5+10;
typedef long long LL;
LL A[maxn],B[maxn],ans = 0;//分别记录分子,分母,ans表示所有重复数字的平方和 
struct Node{
	int id,l,r;
}cur[maxn];
int qu[maxn]={0},col[maxn]={0},num[maxn]={0};//col记录颜色,qu将n个颜色划分为unit个不同的区块,num记录每种颜色的数量。 
LL Gcd(LL x,LL y){
	return y?Gcd(y,x%y):x;
}
bool cmp(Node a,Node b){ //将所有记录的左右边界所属的不同区块划分 
	if(qu[a.l]==qu[b.l]) return a.r<b.r;
	return a.l<b.l;
}
LL f(LL x){
	return x*x;
}
void Add(int x,int d){
	ans -= f(num[col[x]]);
	num[col[x]] += d;
	ans += f(num[col[x]]);
} 
int main(void)
{
	int n,m;
	scanf("%d%d",&n,&m);
	int unit = sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&col[i]);qu[i] = i/unit+1;
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&cur[i].l,&cur[i].r);cur[i].id = i;//离线记录每次询问的左右区间,每次询问的id 
	}
	sort(cur+1,cur+1+m,cmp);//将询问分区 
	int l = 1,r = 0;//起始的区间,ans = 0,因为没有符合要求的数据存在。 
	for(int i=1;i<=m;i++){
		
		if(cur[i].l==cur[i].r){ //特殊情况的判断,只能选一个,结果为0 
			A[cur[i].id] = 0;B[cur[i].id] = 1;continue;
		}
		
		while(l<cur[i].l){ //左区间左移更新 
			Add(l,-1);l++; 
		}
		while(l>cur[i].l){ //左区间右移更新 
			Add(l-1,1);l--;
		}
		while(r<cur[i].r){ //右区间左移更新 
			Add(r+1,1);r++;
		}
		while(r>cur[i].r){ //右区间右移更新 
			Add(r,-1);r--; 
		}
		//以上的区间更新的过程的同时不断更改这个区间内符合要求的ans,(ans-区间大小就是这个区间内符合要求的分子数)。 
		
		A[cur[i].id] = ans - (cur[i].r-cur[i].l+1);
		B[cur[i].id] = 1LL*(cur[i].r-cur[i].l+1)*(cur[i].r-cur[i].l);
		LL gcd = Gcd(A[cur[i].id],B[cur[i].id]);//求公约数,化简分数。 
		A[cur[i].id] /= gcd;
		B[cur[i].id] /= gcd;
	}
	for(int i=1;i<=m;i++) printf("%lld/%lld\n",A[i],B[i]);
	return 0;
} 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值