【ybt金牌导航6-4-4】【luogu P1494】小明选袜子 / 小Z的袜子(莫队)

这篇博客介绍了一种解决区间内颜色匹配概率问题的算法。通过维护每个颜色出现的次数,计算区间内任意两个颜色相同的概率。算法利用了莫队算法的思想,动态更新区间颜色的增减对概率的影响。代码实现中涉及到区间查询、贡献计算和高精度除法优化。
摘要由CSDN通过智能技术生成

小明选袜子 / 小Z的袜子

题目链接:ybt金牌导航6-4-4 / luogu P1494

题目大意

有一排颜色,然后多组询问每次给你区间,问你这个区间中随便选两个,是同一个颜色的概率。

思路

其实就是普通的莫队?

我们考虑某个颜色多了一个和少了一个的贡献要如何计算。

首先不难想到如果一个区间的长度是 n n n,每个颜色出现的个数为 x i x_i xi,那贡献就是 ∑ ( x i ( x i − 1 ) / 2 ) n ( n − 1 ) / 2 = ∑ x i ( x i − 1 ) n ( n − 1 ) \dfrac{\sum(x_i(x_i-1)/2)}{n(n-1)/2}=\dfrac{\sum x_i(x_i-1)}{n(n-1)} n(n1)/2(xi(xi1)/2)=n(n1)xi(xi1)

那我们可以只维护上面的部分,如果 x i x_i xi 变成 x i + 1 x_i+1 xi+1,它的贡献就由 x i ( x i − 1 ) x_i(x_i-1) xi(xi1) 变成 ( x i + 1 ) x i (x_i+1)x_i (xi+1)xi,多了 2 x i 2x_i 2xi
同理,如果 x i x_i xi 变成 x i − 1 x_i-1 xi1,它的贡献就由 x i ( x i − 1 ) x_i(x_i-1) xi(xi1) 变成 ( x i − 1 ) ( x i − 2 ) (x_i-1)(x_i-2) (xi1)(xi2),少了 2 x i 2x_i 2xi

然后这么维护就可以啦。

代码

#include<cmath>
#include<cstdio>
#include<algorithm>
#define ll long long

using namespace std;

struct node {
	int l, r, pl;
}q[50001];
struct answer {
	ll fm, fz;
}an[50001];
int n, m, c[50001], sz, l, r;
int bl[50001], num[50001];
bool in[50001];
ll ans;

bool cmp(node x, node y) {
	if (bl[x.l] != bl[y.l]) return bl[x.l] < bl[y.l];
	return x.r < y.r;
}

ll gcd(ll x, ll y) {
	if (!y) return x;
	return gcd(y, x % y);
}

void ck(answer &x) {
	ll tmp = gcd(x.fz, x.fm);
	x.fz /= tmp; x.fm /= tmp;
}

void change(int pl) {
	if (in[pl]) {
		num[c[pl]]--;
		ans -= 2ll * num[c[pl]];
	}
	else {
		ans += 2ll * num[c[pl]];
		num[c[pl]]++;
	}
	in[pl] ^= 1;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
	
	sz = sqrt(n);
	for (int i = 1; i <= n; i++) bl[i] = (i - 1) / sz + 1;
	
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &q[i].l, &q[i].r);
		q[i].pl = i;
	}
	sort(q + 1, q + m + 1, cmp);
	
	l = 1; r = 0; ans = 0;
	for (int i = 1; i <= m; i++) {
		while (l < q[i].l) change(l), l++;
		while (l > q[i].l) l--, change(l);
		while (r < q[i].r) r++, change(r);
		while (r > q[i].r) change(r), r--;
		an[q[i].pl].fz = ans;
		an[q[i].pl].fm = 1ll * (q[i].r - q[i].l) * (q[i].r - q[i].l + 1);
//		if (l == r) an[q[i].pl] = (answer){1, 0};//luogu上有这句话,一本通上没有 
		ck(an[q[i].pl]);
	}
	
	for (int i = 1; i <= m; i++)
		printf("%lld/%lld\n", an[i].fz, an[i].fm);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值