CF1007B Pave the Parallelepiped

CF1007B Pave the Parallelepiped

题目大意

题目链接

\(T\) 组询问。每次给出三个正整数 \(A, B, C\),代表一个长宽高分别为 \(A, B, C\) 的长方体 \(X\)。请你求出有多少组正整数 \((a, b, c)\) 满足 \(1\leq a\leq b\leq c\),且长宽高分别为 \(a, b, c\) 的长方体 \(Y\) 能被密铺在 \(X\) 中。

密铺的定义是,可以以任意方向摆放 \(Y\)\(6\) 面里任意一面向下),但是用到的所有 \(Y\) 的摆放方向必须一样,按这种方式,用 \(Y\)\(X\) 填满。

数据范围:\(1\leq T\leq 10^5\)\(1\leq A,B,C\leq 10^5\)

本题题解

问题相当于,给定三个整数 \(A, B, C\),求满足 \(a|A, b|B, c|C\)无序三元组 \(\{a, b, c\}\) 的数量。换句话说,\(\{1, 2, 3\}\)\(\{1, 3, 2\}\) 被认为是同一个三元组。

先求出 \(A, B, C\) 的所有约数。

考虑一个简化的问题:假设每个约数,都在 \(A, B, C\) 里都出现过(也就是 \(A = B = C\))。设有 \(n\) 个不同的约数,问题相当于从 \(n\) 个数里,选择 \(k = 3\) 个数,可以重复,能得到的无序 \(k\) 元组数量。记这个问题的答案为 \(f(n, k)\)

这是一个经典问题。设每个数被选中的次数分别为 \(x_1, x_2,\dots, x_n\),则 \(x_1+x_2+\dots+x_n = k\)\(\forall i:x_i\geq 0\)。那么原问题相当于【\(n\) 个非负整数,和为 \(k\) 的方案数】。也就是把 \(k\) 个球,分为 \(n\) 份,每份可以为空的方案数。先强行给每份添加一个球,于是就是把 \(k + n\) 个球,分为 \(n\) 份,每份不可以为空的方案数。由插板法可得:\(f(n, k) ={k + n - 1\choose n - 1} = {k + n - 1\choose k}\)

但是事实上并非每个约数都在 \(A, B, C\) 里都出现过,上述做法里,我们可能会多统计一些答案。对于一道计数题,在这种情况下,往往有两种思路:(1) 容斥原理,即减去不合法的方案;(2) 不重不漏,也就是通过巧妙的枚举方式,使得每种方案恰好被算一次。在本题里,使用容斥原理比较麻烦,需要大量的讨论。故我们考虑不重不漏地计数。

对每个约数,给他一个 \(3\) 位二进制码:

  • 二进制码的第 \(1\) 位为 \(1\),表示它是 \(A\) 的约数。
  • 二进制码的第 \(2\) 位为 \(1\),表示它是 \(B\) 的约数。
  • 二进制码的第 \(3\) 位为 \(1\),表示它是 \(C\) 的约数。

例如:\(101\) 表示该数是 \(A\)\(C\) 的约数,但不是 \(B\) 的约数。

按照二进制码(共有 \(7\) 种),将数划分为 \(7\) 类。设每一类分别有 \(\mathrm{cnt}_1, \mathrm{cnt}_2,\dots, \mathrm{cnt}_7\) 个数。

枚举无序三元组中,三个数分别属于哪一类,分别记为 \(t_1, t_2, t_3\)。为了避免重复计数,我们规定:\(t_1\leq t_2\leq t_3\)。显然必须满足,至少存在一种对应关系 \(p\)(是一个 \(1, 2, 3\) 的排列),使得 \(t_{p_1}, t_{p_2}, t_{p_3}\) 的第 \(1, 2, 3\) 位分别为 \(1\)(也就是三元组 \(p_1, p_2, p_3\) 位置上分别是 \(A, B, C\) 的约数)。

求三个数分别属于 \(t_1, t_2, t_3\) 的三元组数量。考虑 \(t_1, t_2, t_3\) 中每种类别有几个。设类别 \(i\)\(u_i = \sum_{j = 1}^{3} [t_j = i]\) 个,则方案数是 \(f(\mathrm{cnt}_i, u_i)\)。将每种类别的方案数相乘,就是符合 \(t_1, t_2, t_3\) 的三元组数量。 累加到答案里即可。

形式化地说:

\[\mathrm{ans} = \sum_{t_1, t_2, t_3}\prod_{i = 1}^{7}f(\mathrm{cnt}_i, \sum_{j = 1}^{3}[t_j = i]) \]

最后,发现我们其实不需要求出 \(A, B, C\) 的所有约数具体是什么,而只需要求出 \(\mathrm{cnt}_1, \mathrm{cnt}_2,\dots, \mathrm{cnt}_7\)。预处理出每个数的约数数量 \(d(x)\)(可以线性筛)。每组询问时,用 \(\gcd\) 计算出 \(\mathrm{cnt}\) 数组,例如:\(5 = (101)_2\),则 \(\mathrm{cnt}_5 = d(\gcd(A, C)) - d(\gcd(A, B, C))\)

\(n = \max\{A, B, C\}\)。时间复杂度 \(\mathcal{O}(n + q\log n)\)。此外,因为要枚举 \(t_1, t_2, t_3\) 并计算方案,每组询问有常数大约 \(7^3\cdot 7\),但是因为强制了 \(t_1\leq t_2\leq t_3\),所以实际更小。

参考代码

实际提交时建议使用读入、输出优化,详见本博客公告。

// problem: CF1007B
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 1e5;

int p[MAXN + 5], cnt_p;
bool v[MAXN + 5];
int d[MAXN + 5], num[MAXN + 5];
void sieve() {
	d[1] = 1;
	for (int i = 2; i <= MAXN; ++i) {
		if(!v[i]) {
			p[++cnt_p] = i;
			num[i] = 1;
			d[i] = 2;
		}
		for (int j = 1; j <= cnt_p && i * p[j] <= MAXN; ++j) {
			v[i * p[j]] = 1;
			if (i % p[j] == 0) {
				num[i * p[j]] = num[i] + 1;
				d[i * p[j]] = d[i] / (num[i] + 1) * (num[i] + 2);
				break;
			}
			num[i * p[j]] = 1;
			d[i * p[j]] = d[i] * 2;
		}
	}
}

int q, a, b, c;
int cnt[8], used[8];

int t[5];
bool check() {
	static int p[5];
	for (int i = 1; i <= 3; ++i) {
		p[i] = i;
	}
	do {
		if ((t[p[1]] & 1) && (t[p[2]] & 2) && (t[p[3]] & 4)) {
			return true;
		}
	} while (next_permutation(p + 1, p + 3 + 1));
	return false;
}

int gcd(int x, int y) {
	return (!y) ? x : gcd(y, x % y);
}
ll comb(int n, int k) {
	ll res = 1;
	for (int i = n; i >= n - k + 1; --i) {
		res *= i;
	}
	for (int i = k; i >= 1; --i) {
		res /= i;
	}
	return res;
}

int main() {
	sieve();
	
	cin >> q;
	while (q--) {
		cin >> a >> b >> c;
		
		int gab = gcd(a, b);
		int gac = gcd(a, c);
		int gbc = gcd(b, c);
		int gabc = gcd(gab, c);
		
		cnt[1] = d[a] - d[gab] - d[gac] + d[gabc];
		cnt[2] = d[b] - d[gab] - d[gbc] + d[gabc];
		cnt[4] = d[c] - d[gac] - d[gbc] + d[gabc];
		cnt[3] = d[gab] - d[gabc];
		cnt[5] = d[gac] - d[gabc];
		cnt[6] = d[gbc] - d[gabc];
		cnt[7] = d[gabc];
		
		ll ans = 0;
		for (t[1] = 1; t[1] <= 7; ++t[1]) {
			for (t[2] = t[1]; t[2] <= 7; ++t[2]) {
				for (t[3] = t[2]; t[3] <= 7; ++t[3]) {
					if (check()) {
						memset(used, 0, sizeof(used));
						used[t[1]]++;
						used[t[2]]++;
						used[t[3]]++;
						ll cur = 1;
						for (int i = 1; i <= 7; ++i) {
							cur *= comb(cnt[i] + used[i] - 1, used[i]);
						}
						ans += cur;
					}
				}
			}
		}
		
		cout << ans << endl;
	}
	return 0;
}
posted @ 2021-02-27 10:35  duyiblue  阅读(171)  评论(0编辑  收藏  举报