组合计数做题笔记

\(\color{#FFC116}(1)\) CF1400D Zigzags

  • 给出 \(n\) 个数 \(a_1,a_2,\cdots,a_n\)。求问有多少个四元组 \((i,j,k,l)\),使得这个四元组满足下列条件:

    • \(1 \leq i<j<k<l \leq n\)
    • \(a_i=a_k\) 并且 \(a_j=a_l\)
  • \(a_i \le n \le 3000\)

显然可以枚举 \(j, k\),所以此时 \(a_j, a_k\) 为定值。那么 \(i\) 必须要满足 \(i < j\)\(a_i = a_k\)\(l\) 必须要满足 \(l > k\)\(a_l = a_j\)。所以我们要做的就是统计一段区间内某个数字的出现次数。

套路。用 vector 存储每个数字出现的位置,那么每次查询相当于是问某个 vector 中有多少数在 \([l, r]\) 范围内。二分即可。

$\color{blue}\text{Code}$
vector<int> mp[N];

int calc(int l, int r, int x) {
	if (!mp[x].size()) return 0;
	return upper_bound(mp[x].begin(), mp[x].end(), r) - lower_bound(mp[x].begin(), mp[x].end(), l);
}

void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i <= n; ++ i ) {
		fin >> a[i];
		mp[a[i]].push_back(i);
	}
	
	res = 0;
	for (int j = 2; j < n; ++ j )
		for (int k = j + 1; k < n; ++ k )
			res += calc(1, j - 1, a[k]) * 1.0 * calc(k + 1, n, a[j]);
	
	fout << res << '\n';
	
	for (int i = 1; i <= n; ++ i ) mp[i].clear();
}

\(\color{#52A41A} (2)\) CF1327E Count The Blocks

  • 对于一个数 \(t\),定义一个块是 \(t\) 中的连续相等的一串数字。写下 \(0\cdots10^n-1\) 的所有数,不足 \(n\) 位的用前导 \(0\) 补足 \(n\) 位。对于每个 \(i=1\cdots n\),求这 \(10^n\) 个数中一共有多少个长为 \(i\) 的块。
  • \(n \le 2 \times 10^5\)

首先发现 \(10^n\) 个数是假的,你可以将其看作是有 \(n\) 个位置,每个位置可以填 \(0 \sim 9\) 的数字。

显然枚举题中的 \(i\)。分析这 \(n\) 个数字:

  • 有连续的 \(i\) 个数字是相同的,也就是说有 \(10\) 种取值;

  • 这个长度为 \(i\) 的块的左边的位置(可能不存在)不能填这个数字,即有 \(9\) 种取值;

  • 这个长度为 \(i\) 的块的右边的位置(可能不存在)不能填这个数字,即有 \(9\) 种取值;

  • 其余的位置任意填,即每个位置都有 \(10\) 种取值。

然后乘法原理即可。

$\color{blue}\text{Code}$
void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i < n; ++ i ) {
		fout << 10ll * ((n - i - 1) * 81ll % P * fpm(10, n - i - 2) % P + 18ll * fpm(10, n - i - 1) % P) % P << ' ';
	}
	puts("10");
}

\(\color{#52A41A}(3)\) CF1545B AquaMoon and Chess

  • 有一个 \(1 \times n\) 的棋盘。有的格子上有棋子。你可以选择一个有棋子的位置 \(i\),并进行以下操作:

    • 如果 \(i+2 \leq n\)\((i+1)\) 上有棋子,且 \((i+2)\) 上没有棋子,将棋子移动到 \((i + 2)\)
    • 如果 \(i-2 \ge 1\)\((i-1)\) 上有棋子,且 \((i-2)\) 上没有棋子,将棋子移动到 \((i - 2)\)

    求最终局面数。

  • \(n \le 10^5\)

首先发现棋子的移动可以看作是 \([\texttt{1 1}]\) 的移动。例如:

\[\begin{matrix} 0&\color{red}[1&\color{red}1]&0&1\end{matrix} \Longrightarrow \begin{matrix} 0&0&\color{red}[1&\color{red}1]&1\end{matrix} \]

但是还会有一些剩余的 \([\texttt{1}]\),例如:

\[\begin{matrix} \color{red}[1&\color{red}1]&\color{blue}1&0&1\end{matrix} \Longrightarrow \begin{matrix} \color{blue}1&\color{red}[1&\color{red}1]&0&1 \end{matrix} \Longrightarrow \begin{matrix} \color{blue}1&0&\color{red}[1&\color{red}1]&1 \end{matrix} \]

发现这个单独的 \([\texttt{1}]\) 的位置是可以随 \([\texttt{1 1}]\) 而唯一确定的。所以我们只需要考虑 \([\texttt{1 1}]\)

设有 \(a\)\([\texttt{1 1}]\)(也就是有 \(2a\) 个数),\(b\) 个剩余的 \([\texttt1]\),那么答案为 \(\dbinom{n-a-b}a\),表示将这 \(a\)\([\texttt{1 1}]\) 随便放。

$\color{blue}\text{Code}$
void Luogu_UID_748509() {
	scanf("%d%s", &n, s + 1);
	int a = 0, b = 0;
	for (int i = 1; i <= n; ++ i ) {
		if (i < n && s[i] == '1' && s[i + 1] == '1') {
			++ a;
			++ i;
		}
		else if (s[i] == '1') {
			++ b;
		}
	}

	fout << C(n - a - b, a) << '\n';
}

\(\color{#9D3DCF}(4)\) CF1245F Daniel and Spring Cleaning

  • 给定 \(l, r\),求 \(\sum_{i=l}^r \sum_{j=l}^r [i+j = i \operatorname{xor} j]\)
  • \(r \le 10^9\)

因为异或是不进位加法,所以只要 \(i, j\) 进行加法时不进位,就一定有 \(i+j = i \operatorname{xor} j\)。换言之,\(i \operatorname{and} j = 0\)\(i+j = i \operatorname{xor} j\) 是等价的。

所以所求即 \(\sum_{i=l}^r \sum_{j=l}^r [i \operatorname{and} j = 0]\)

\(f(x, y) = \sum_{i=\color{red}1}^x \sum_{j=\color{red}1}^y [i \operatorname{and} j = 0]\)。那么所求即 \(f(r, r) - f(r, l - 1) - f(l - 1, r) + f(l - 1, l - 1)\)\(f(x, y)\) 的求解是简单数位 DP。

$\color{blue}\text{Code}$
int a[50], b[50], len;
int f[50][2][2];

int dp(int u, bool sb, bool bs) {
	if (!u) return 1;
	
	int& res = f[u][sb][bs];
	if (~res) return res;
	res = 0;
	
	int t1 = sb ? a[u] : 1;
	int t2 = bs ? b[u] : 1;
	
	for (int i = 0; i <= t1; ++ i )
		for (int j = 0; j <= t2; ++ j )
			if (!i || !j) res += dp(u - 1, sb && (i == t1), bs && (j == t2));
	
	return res;
}

int calc(int x, int y) {
	if (x < 0 || y < 0) return 0;
	
	memset(f, -1, sizeof f);
	memset(a, 0, sizeof a);
	memset(b, 0, sizeof b);
	
	int l = 0;
	while (x) {
		a[ ++ l] = x & 1;
		x >>= 1;
	}
	len = l, l = 0;
	while (y) {
		b[ ++ l] = y & 1;
		y >>= 1;
	}
	len = max(len, l);
	
	int res = dp(len, 1, 1);
	
	return dp(len, 1, 1);
}

void Luogu_UID_748509() {
	int l, r;
	fin >> l >> r;
	fout << (calc(l - 1, l - 1) + calc(r, r)) - (calc(l - 1, r) + calc(r, l - 1)) << '\n';
}

\(\color{#52A41A}(5)\) CF1444B Divide and Sum

  • 给一个长度为 \(2n\) 的数列 \(a\),将 \(a\) 中的数分为两串长度为 \(n\) 的数列 \(p\)\(q\)。将 \(p\) 升序排序,\(q\) 降序排序。定义权值为 \(\sum_{i=1}^n |p_i - q_i|\)。求所有划分的权值和。
  • \(n \le 1.5 \times 10^5\)

首先每种划分的方案都是一定的,为 \(\sum_{v \in S_1} v - \sum_{v \in S_2} v\)。其中 \(S_1\) 表示 \(a\) 的前 \(n\) 大的数构成的集合,\(S_2\) 表示 \(a\) 的前 \(n\) 小的数构成的集合。

那么答案为 \(\dbinom {2n}n \cdot \sum_{v \in S_1} v - \sum_{v \in S_2} v\)

首先 \(\sum |p_i - q_i| = \sum (\max (p_i,q_i) - \min(p_i, q_i))\),而且可以证明 \(\max (p_i,q_i) \in S_1\) 以及 \(\min(p_i, q_i) \in S_2\)

考虑反证:

  • 如果 \(\max (p_i,q_i), \min(p_i, q_i) \in S_1\),那么一定存在某个 \(j\) 使得 \(\max (p_j, q_j), \min(p_j, q_j) \in S_2\)。那么此时 \(i < j\)\(i > j\) 都不能满足「\(p\) 升序排序,\(q\) 降序排序」这个条件。
  • 如果 \(\max (p_i,q_i), \min(p_i, q_i) \in S_2\),那么一定存在某个 \(j\) 使得 \(\max (p_j, q_j), \min(p_j, q_j) \in S_1\)。那么此时 \(i < j\)\(i > j\) 都不能满足「\(p\) 升序排序,\(q\) 降序排序」这个条件。
$\color{blue}\text{Code}$
void Luogu_UID_748509() {
	fin >> n;
	for (int i = 1; i <= n * 2; ++ i ) fin >> a[i];
	sort(a + 1, a + n * 2 + 1);
	int res = 0;
	for (int i = 1; i <= n; ++ i ) (res += a[i + n] - a[i]) %= P;
	fout << (ll)res * C(2 * n, n) % P << '\n';
}

\(\color{#3498D8}(6)\) P9489 ZHY 的表示法

  • 定义:
    • 一个实数 \(y\)表示一个正整数 \(x\),当且仅当 \(\lfloor \frac y {x_1} \rfloor+\lfloor \frac y {x_2} \rfloor+\cdots + \lfloor \frac y {x_n} \rfloor=x\)
    • 一个正整数 \(x\)可被表示的,当且仅当存在一个实数 \(y\)表示 \(x\)
  • \(\color{red}n \le 25\)\(r, x_i \le 10^9\)

首先差分,设 \(f(x)\) 表示 \(1 \sim x\)可被表示的整数数量,那么 \(f(r) - f(l - 1)\) 即为答案。接下来考虑求解 \(f(x)\)

首先因为有下取整,所以 \(y\) 是一般实数还是整数就无所谓了。只需要考虑 \(y \in \mathbb N\) 的情况。

二分出最大的 \(z\) 使得 \(\sum \lfloor \frac z{y_i} \rfloor \le x\)。那么如果 \(1 \sim x\) 中的某个数是可被表示的,就一定能被某个 \(1 \sim z\) 中的数表示

我们定义:

  • 一个实数 \(y\) 能够严格表示一个正整数 \(x\),当且仅当 \(y\) 为能表示 \(x\) 的实数中的最小值。
  • 一个实数 \(y\)可严格表示的,当且仅当存在一个正整数 \(x\) 使得 \(y\) 可以严格表示 \(x\)

那么可被表示的数和可严格表示的数一一对应。所以问题转化成 \(1 \sim z\) 中有多少可严格表示的数。

感性理解如果 \(t \in [1, z]\)\(t\)可严格表示的的,那么 \(t\) 一定是至少一个 \(x_i\) 的倍数。于是就可以容斥了:

\[\sum_{S \subseteq \{1, 2, \dots, n\}} (-1)^{|S| + 1} \left\lfloor \dfrac {z}{\operatorname{lcm}_{v \in S}\{x_v\}}\right\rfloor \]

$\color{blue}\text{Code}$
int n, l, r, x[N];

bool chk(int xx, int mid) {
	int res = 0;
	for (int i = 1; i <= n; ++ i ) res += mid / x[i];
	return res <= xx;
}

int lcm(int a, int b) {
	return a * b / __gcd(a, b);
}

int find(int xx) {
	int res = 0;
	for (int s = 1; s < (1 << n); ++ s ) {
		int cnt = __builtin_popcount(s), sum = 1;
		for (int i = 1; i <= n; ++ i )
			if (s >> (i - 1) & 1)	{
				sum = lcm(sum, x[i]);
				if (sum > xx) break;
			}
		if (cnt & 1) res += xx / sum;
		else res -= xx / sum;
	}
	return res;
}

int solve(int x) {
	int l = 1, r = 1e18, res;
	while (l <= r) {
		int mid = l + r >> 1;
		if (chk(x, mid)) res = mid, l = mid + 1;
		else r = mid - 1;
	}
	return find(res);
}

void Luogu_UID_748509() {
	fin >> n >> l >> r;
	for (int i = 1; i <= n; ++ i ) fin >> x[i];
	fout << solve(r) - solve(l - 1) << '\n';
}

\(\color{#52A41A}(7)\) CF1426F Number of Subsequences

  • 一个包含 \(\texttt{a,b,c,?}\) 的字符串。对于所有把 \(\texttt ?\) 替换为 \(\texttt{a,b,c}\) 中一个的情形,求子序列 \(\texttt{abc}\) 出现的次数之和。
  • \(n \le 2 \times 10^5\)

枚举 \(\texttt b\) 的位置 \(i\)。显然这一位需要是 \(\texttt {b,?}\)。那么我们需要在 \([1, i - 1]\) 中找 \(\texttt a\),在 \([i + 1, n]\) 中找 \(\texttt c\)

\(f(i, c)\) 表示 \([1, i - 1]\)\(c\) 的出现次数,\(g(i, c)\) 表示 \([i + 1, n]\)\(c\) 的出现次数。那么位置 \(i\) 的贡献为:

\[f(i, \texttt a) \times g(i, \texttt c) \times 3^{f(i, \texttt ?) + g(i, \texttt ?)} + f(i, \texttt a) \times g(i, \texttt ?) \times 3^{f(i, \texttt ?) + g(i, \texttt ?) - 1} + f(i, \texttt ?) \times g(i, \texttt c) \times 3^{f(i, \texttt ?) + g(i, \texttt ?) - 1} + f(i, \texttt ?) \times g(i, \texttt ?) \times 3^{f(i, \texttt ?) + g(i, \texttt ?) - 2} \]

$\color{blue}\text{Code}$
#include <bits/stdc++.h>

using namespace std;

constexpr int P = 1e9 + 7;
typedef long long ll;

int main() {
	int n, res = 0;
	cin >> n;
	string s;
	cin >> s;
	
	vector<int> fa(n), gc(n), fw(n), gw(n);
	for (int i = 0; i < n; ++ i ) {
		fa[i] = (i ? fa[i - 1] : 0) + (s[i] == 'a');
		fw[i] = (i ? fw[i - 1] : 0) + (s[i] == '?');
	}
	
	for (int i = n - 1; ~i; -- i ) {
		gc[i] = (i != n - 1 ? gc[i + 1] : 0) + (s[i] == 'c');
		gw[i] = (i != n - 1 ? gw[i + 1] : 0) + (s[i] == '?');
	}
	
	auto fpm = [&](int b) {
		if (b < 0) return 0;
		int res = 1, a = 3;
		while (b) {
			if (b & 1) res = (ll)res * a % P;
			b >>= 1, a = (ll)a * a % P;
		}
		return res;
	};
	
	auto f = [&](int i, char c) {
		return !i ? 0 : (c == 'a' ? fa[i - 1] : fw[i - 1]);
	};
	
	auto g = [&](int i, char c) {
		return i == n - 1 ? 0 : (c == 'c' ? gc[i + 1] : gw[i + 1]);
	};
	
	for (int i = 0; i < n; ++ i )
		if (s[i] == 'b' || s[i] == '?') {
			int s1 = (ll)f(i, 'a') * g(i, 'c') % P * fpm(f(i, '?') + g(i, '?')) % P;
			int s2 = (ll)f(i, 'a') * g(i, '?') % P * fpm(f(i, '?') + g(i, '?') - 1) % P;
			int s3 = (ll)f(i, '?') * g(i, 'c') % P * fpm(f(i, '?') + g(i, '?') - 1) % P;
			int s4 = (ll)f(i, '?') * g(i, '?') % P * fpm(f(i, '?') + g(i, '?') - 2) % P;
			res = ((((res + s1) % P + s2) % P + s3) % P + s4) % P;
		}
	
	cout << res;
	
	return 0;
}

\(\color{#3498D8}(8)\) CF1366E Two Arrays

  • 给定一个长为 \(n\) 的数组 \(a\) 和一个长为 \(m\) 的数组 \(b\),你要把 \(a\) 分割为连续的 \(m\) 段使得第 \(i\) 段中的最小值恰为 \(b_i\) ,求总方案数。保证 \(b\) 严格单调递增。
  • \(m \le n \le 2 \times 10^5\)\(a_i, b_i \le 10^9\)

首先特判掉 \(\min a \ne b_1\) 无解。

思考如果 \(a_j < b_i\),那么 \(j\) 一定会被分到 \(1 \sim i - 1\) 中的某个组。因此 \(a_{j - 1}, a_{j - 2}, \dots\) 等之前的也一定会被分到 \([1, i - 1]\) 的组中。

这启发我们求出 \(a\) 的后缀最小值 \(c_i = \min_{j=i}^n a_j\)。若 \(c_{l_i - 1} \ne b_i = c_{l_i} = c_{l_{i+1}} = \dots = c_{r_i} \ne c_{r_i + 1}\),那么我们可以在 \(l_i \sim r_i\) 中任意一个位置结束第 \(i\) 段。令 \(l_i \sim r_i\) 的长度为 \(t_i\),即 \(b_i\)\(c\) 中出现了 \(t_i\) 次,那么答案为:

\[\sum_{i=2}^n t_i \]

posted @ 2024-05-23 13:04  2huk  阅读(53)  评论(0编辑  收藏  举报