Java教程

【日常训练】取数问题

本文主要是介绍【日常训练】取数问题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Description

给出一个长度为 \(n\) 的序列 \(a\),接下来会有 \(m\) 次询问。

每次询问会给出一个区间 \([l, r]\) 和一个数 \(x\),你的任务如下。

  • 给出一种取数的方式:

    • 从区间 \([1, r - l + 1]\) 等概率地选取一个数 \(K\)。
    • 从区间 \([l, r]\) 内等概率地选取 \(K\) 个数。
  • 你需要回答:选取的所有数都大于等于 \(x\) 的概率。

答案对 \(998244353\) 取模。

数据范围:\(1 \leq n, m \leq 10^6\),\(1 \leq a_i, x \leq 10^5\)。
时空限制:\(1000 \ \text{ms} / 512 \ \text{MiB}\)。

Solution

对于任意一组询问 \((l, r, x)\)。
我们记区间长度为 \(L\),区间中大于等于 \(x\) 的数有 \(H\) 个。

那么答案为:

\[\frac{1}{L}\sum\limits_{i = 1}\limits^{H} \frac{\tbinom{H}{i}}{\tbinom{L}{i}} \]

将组合数拆开,化简,再配成组合数:

\[\begin{aligned}\frac{1}{L}\sum\limits_{i = 1}^{H} \frac{\binom{H}{i}}{\tbinom{L}{i}}& = \frac{1}{L}\sum\limits_{i = 1}^H \frac{\frac{H!}{i!(H-i)!}}{\frac{L!}{i!(L-i)!}} \\& = \frac{1}{L}\sum\limits_{i = 1}^H \frac{H!(L-i)!}{(H - i)!L!} \\& = \frac{H!}{L \cdot L!}\sum\limits_{i = 1}^H \frac{(L - i)!}{(H - i)!} \\& = \frac{H!(L - H)!}{L \cdot L!} \sum\limits_{i = 1}^{H}\frac{(L - i)!}{(H - i)!(L - H)!} \\& = \frac{H!(L - H)!}{L \cdot L!} \sum\limits_{i = 1}^{H} \dbinom{L - i}{H - i}\end{aligned} \]

重点在于后半段,把它写开就是:

\[\dbinom{L - H}{0} + \dbinom{L - H + 1}{1} + \dbinom{L - H + 2}{2} + \cdots + \dbinom{L - 1}{H - 1} \]

观察到第一项组合数的选数为 \(0\),那么可以把总数调成 \(L - H + 1\)。
答案就可以变化为:

\[\dbinom{L - H + 1}{0} + \dbinom{L - H + 1}{1} + \dbinom{L - H + 2}{2} + \cdots + \dbinom{L - 1}{H - 1} \]

然后,由大家都知道的 \(\tbinom{n}{m} = \tbinom{n - 1}{m} + \tbinom{n - 1}{m - 1}\)。
答案就可以变化为:

\[\dbinom{L - H + 2}{1} + \dbinom{L - H + 2}{2} + \cdots + \dbinom{L - 1}{H - 1} \]

然后继续:

\[\dbinom{L - H + 3}{2} + \cdots + \dbinom{L - 1}{H - 1} \]

继续继续继续继续继续 ......
最后得到:

\[\dbinom{L}{H - 1} \]

然后原式就变化为:

\[\begin{aligned}\frac{H!(L - H)!}{L \cdot L!}\dbinom{L}{H - 1}& = \frac{H!(L - H)!}{L \cdot L!}\frac{L!}{(H - 1)!(L - H + 1)!} \\& = \frac{H}{L(L - H + 1)}\end{aligned} \]

先预处理出 \(1 \sim n\) 在模 \(998244353\) 意义下的逆元。如果知道了 \(L\) 和 \(H\) 就可以 \(\mathcal{O}(1)\) 算出答案。

于是,对于每一组询问 \((l, r, x)\)。
就是要求出区间 \([l, r]\) 内有多少个数大于等于 \(x\)。

在线的话就直接主席树。但是这题 ... 鄙人亲测会被卡。
离线的话就直接从大到小加数,用 BIT 维护。

时间复杂度 \(\mathcal{O}((n + m) \log n)\),非常的优秀。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

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

int power(int a, int b, int p) {
	int ans = 1;
	for (; b; b >>= 1) {
		if (b & 1) ans = 1ll * ans * a % p;
		a = 1ll * a * a % p;
	}
	return ans;
}

const int N = 1000100;
const int SIZE = 100100;
const int mod = 998244353;

int n, m;

int a[N];
int inv[N];

struct range {
	int l, r, id;

	range() {}
	range(int A, int B, int C) : l(A), r(B), id(C) {}
};

vector<int> H[SIZE];
vector<range> attend[SIZE];

int c[N];

void add(int x, int val) {
	for (; x <= n; x += x & -x) c[x] += val;
}

int ask(int x) {
	int ans = 0;
	for (; x; x -= x & -x) ans += c[x];
	return ans;
}

int cur[N];

int main() {
	n = read(), m = read();

	for (int i = 1; i <= n; i ++)
		inv[i] = power(i, mod - 2, mod);

	for (int i = 1; i <= n; i ++)
		a[i] = read();

	for (int i = 1; i <= n; i ++)
		H[a[i]].push_back(i);

	for (int i = 1; i <= m; i ++) {
		int l = read(), r = read(), x = read();
		attend[x].push_back((range) { l, r, i });
	}

	for (int x = 100000; x >= 1; x --) {
		for (int i = 0; i < (int)H[x].size(); i ++) {
			int pos = H[x][i];
			add(pos, 1);
		}

		for (int i = 0; i < (int)attend[x].size(); i ++) {
			range G = attend[x][i];

			int l = G.l, r = G.r;

			int cnt = ask(r) - ask(l - 1),
				len = r - l + 1;

			int val = cnt;
			val = 1ll * val * inv[len] % mod;
			val = 1ll * val * inv[len - cnt + 1] % mod;

			cur[G.id] = val;
		}
	}

	for (int i = 1; i <= m; i ++)
		printf("%d\n", cur[i]);

	return 0;
}

此题严重暴露了我数学水平低下的弱点,中考数学是我最后的遮羞布了。

这篇关于【日常训练】取数问题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!