『莫队·数学推导』小Z的袜子

题目描述

作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……

具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。

你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

然而数据中有L=R的情况,请特判这种情况,输出0/1。

题解

可以说是莫队算法板子题了。

一直区间 [ l , r ] [l,r] [l,r]的答案是 ∑ C c n t x 2 C r − l + 1 2   =   ∑ c n t x 2 − ∑ c n t x ( r − l ) ∗ ( r − l + 1 ) \frac{\sum C_{cnt_x}^{2}}{C_{r-l+1}^{2}}\ =\ \frac{\sum cnt_x^2-\sum cnt_x}{(r-l)*(r-l+1)} Crl+12Ccntx2 = (rl)(rl+1)cntx2cntx.

因此我们只要统计每一个答案中分子的数即可。

如果我们一直[l,r]→[l,r+1],改变的答案计算方式可以是:减去原来的答案,再加上新增加的答案。具体的讲就是: a n s − c n t x 2 + ( c n t x + 1 ) 2 − 1 ans-cnt_x^2+(cnt_x+1)^2-1 anscntx2+(cntx+1)21.如果是[l,r]→[l,r-1],新的答案是: a n s − c n t x 2 + ( c n t x − 1 ) 2 + 1 ans-cnt_x^2+(cnt_x-1)^2+1 anscntx2+(cntx1)2+1.

约分操作只要同时除以分子和分母的最大公因数即可。

代码如下:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define int long long

using namespace std;

struct node {
	int L, R, id, p;
	friend bool operator < (node p1, node p2) {
		if (p1.p == p2.p) return p1.R < p2.R;
		return p1.p < p2.p;
	}
};
int n,m,T;
int ans = 0; 
int a[200000];
node q[200000];
int cnt[200000];
pair<int,int>Ans[200000];

inline int read(void)
{
	int s = 0, w = 1;char c = getchar();
	while (c<'0' || c>'9') {if (c == '-') w = -1; c = getchar();}
	while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
	return s*w;
}

void del(int x)
{
	ans = ans - cnt[x] * cnt[x] + (cnt[x]-1) * (cnt[x]-1) + 1;
	cnt[x] --;
}

void ins(int x)
{
	ans = ans - cnt[x] * cnt[x] + (cnt[x]+1) * (cnt[x]+1) - 1;
	cnt[x] ++;
}

int gcd(int a,int b)
{
	if (b == 0) return a;
	return gcd(b,a%b);
}

signed main(void)
{
	freopen("2012.in","r",stdin);
	freopen("2012.out","w",stdout);
	n = read();
	m = read(); 
	T = sqrt(n);
	for (int i=1;i<=n;++i) a[i] = read();
	for (int i=1;i<=m;++i)
	{
		q[i].L = read();
		q[i].R = read();
		q[i].id = i;
		q[i].p = (q[i].L+T-1) / T;
	}
	sort(q+1, q+m+1);
	ans = 0;
	cnt[a[1]] = 1;
	int l = 1;
	int r = 1;
	for (int i=1;i<=m;++i)
	{
		while (l < q[i].L) del(a[l++]);
		while (l > q[i].L) ins(a[--l]);
		while (r < q[i].R) ins(a[++r]);
		while (r > q[i].R) del(a[r--]);
		int k = (q[i].R - q[i].L) * (q[i].R - q[i].L + 1);
		if (ans == 0) Ans[q[i].id] = make_pair(0,1); 
		else Ans[q[i].id] = make_pair(ans/gcd(ans,k), k/gcd(ans,k));
	}
	for (int i=1;i<=m;++i) printf("%lld/%lld\n", Ans[i].first, Ans[i].second);
	return 0; 
}  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值