hdu 5145 NPY and girls(分块+莫队+逆元)

本文介绍了如何使用分块莫队算法来解决一道编程竞赛题目——编号在指定区间的女生有多少种访问方式的问题。算法通过排序查询区间,采用双指针移动策略,并利用逆元计算排列组合,实现高效求解。代码中展示了具体的实现细节,包括排序规则、指针移动策略和逆元计算。
摘要由CSDN通过智能技术生成

问题

hdu 5145 NPY and Girls - https://acm.hdu.edu.cn/showproblem.php?pid=5145

分析

  • 题意:编号在 [ l , r ] [l,r] [lr] 区间内的女生,有多少种访问方式?是多重集排列问题,计算式: N ! n 1 ! ⋯ n k ! \frac{N!}{n_1!\cdots n_k!} n1!nk!N!
  • 方法:分块
  • 莫队算法
    • 查询区间排序规则
      • 第一关键字:左边界所在的块(升序)
      • 第二关键字:右边界升序(块号偶数)或 降序(块号奇数)
    • 代码
	sort(q+1, q+m+1, [](query x, query y){
		return x.L==y.L?((x.L&1)?x.r>y.r:x.r<y.r):x.L<y.L;
	});
  • 双指针初始化:初始时刻均指向第一个查询的左边界,计数器值为1;
int pl = q[1].l, pr = q[1].l;
num[a[q[1].l]] = 1, ret  = 1;
  • 指针移动方式
    • 区间扩大优先(左指针优先左移,右指针优先右移),使得两指针不会交叉
    • 区间扩大时(先更新指针,然后加入新指元素)
    • 区间减小时(先去除当前所指元素,然后更新指针值)
	while(pl > q[i].l) add(--pl);
	while(pr < q[i].r) add(++pr);
	while(pl < q[i].l) sub(pl++);
	while(pr > q[i].r) sub(pr--);		
  • 逆元:暴力枚举

代码

/* hdu 5145 NPY and girls */
#include<bits/stdc++.h>
using namespace std;
#define MXN 30010
#define MXB 175 // 分块数量
#define siz MXB // 分块大小
#define M 1000000007
int n, m, a[MXN], ans[MXN], num[MXN], ret, inv[MXN], finv[MXN]={1};
struct query{int l, r, L, R, n;} q[MXN];
void add(int p){
	ret = 1LL*ret*inv[++num[a[p]]]%M;
}
void sub(int p){
	ret = 1LL*ret*(num[a[p]]--)%M;
};
void ex_gcd(int aa, int bb, int &x, int &y){
	if(bb == 0) {
		x = 1, y = 0;
		return;
	}
	ex_gcd(bb, aa%bb, x, y);
	int tmp = x;
	x = y;
	y = tmp-aa/bb*y;
}
void solve(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i) scanf("%d", a+i);
	for(int i = 1; i <= m; ++i){
		scanf("%d%d", &q[i].l, &q[i].r), q[i].n = i;
		q[i].L = q[i].l/siz, q[i].R = q[i].r/siz;
	}
	sort(q+1, q+m+1, [](query x, query y){
		return x.L==y.L?((x.L&1)?x.r>y.r:x.r<y.r):x.L<y.L;
	});
	memset(num, 0, sizeof num);
	memset(ans, 0, sizeof ans);
	int pl = q[1].l, pr = q[1].l;
	num[a[q[1].l]] = 1, ret  = 1;
	for(int i = 1; i <= m; ++i){
		while(pl > q[i].l) add(--pl);
		while(pr < q[i].r) add(++pr);
		while(pl < q[i].l) sub(pl++);
		while(pr > q[i].r) sub(pr--);		
		ans[q[i].n] = 1LL*ret*finv[q[i].r-q[i].l+1]%M;
	}
	for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
}
int main(){
	int t;
	for(int i = 1; i < MXN-1; ++i)	
		ex_gcd(i, M, inv[i], inv[i+1]), inv[i] = (inv[i]%M+M)%M;
	for(int i = 1; i < MXN; ++i) finv[i] = 1LL*finv[i-1]*i%M;
	scanf("%d", &t);
	while(t--) solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jpphy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值