#P2915. 第3题-三元组数量
-
1000ms
Tried: 18
Accepted: 2
Difficulty: 10
所属公司 :
阿里
时间 :2025年4月27日-菜鸟(算法岗)
-
算法标签>数学
第3题-三元组数量
题解
题面描述
给定一个正整数 n,求满足以下条件的三元组 (a,b,c) 的数量:
- a+b+c=n。
- a,b,c 是两两不同的正整数。
- a,b,c 的各个数位均不包含数字 2 和 4。
若通过改变三个正整数的顺序可以得到相同的组合,则只计算为一种分解方法。
思路
-
定义“好数”
令

-
生成函数
G(x)=i=0∑∞f[i]xi.
构造 -
容斥计数
- 令 E1[n] 为 G(x)3 在 xn 项的系数,对应“有序三元组(允许相等)”的数量。
- 令 E2[n] 为 G(x)⋅G(x2) 在 xn 项的系数,对应恰有一对相等(如 a=b=c)的有序三元组数量。
- 令
根据容斥原理,有序且互异的三元组数量为
-
去重
6E1[n]−3E2[n]+2E3[n].
有序互异三元组一共有 E1[n]−3E2[n]+2E3[n] 种,不考虑顺序的无序三元组要除以 3!=6,即 -
FFT 卷积
- 直接构造长度不小于 3n 的数组,分别存放 f[i] 和 f[i] “跳步”版(用于计算 E2)。
- 通过 FFT/逆 FFT 快速得到 E1 和 E2,再按上面公式计算即可。
C++
#include <bits/stdc++.h>
using namespace std;
using cd = complex<double>;
// FFT 变换,自底向上实现
void fft(vector<cd>& a, bool inv){
int n = a.size(), j = 0;
// 位反转置换
for(int i = 1; i < n; ++i){
int bit = n >> 1;
for(; j & bit; bit >>= 1) j ^= bit;
j |= bit;
if(i < j) swap(a[i], a[j]);
}
const double PI = acos(-1);
// 迭代合并
for(int len = 2; len <= n; len <<= 1){
double ang = 2 * PI / len * (inv ? -1 : 1);
cd wlen(cos(ang), sin(ang));
for(int i = 0; i < n; i += len){
cd w(1);
for(int j = 0; j < len/2; ++j){
cd u = a[i+j];
cd v = a[i+j+len/2] * w;
a[i+j] = u + v;
a[i+j+len/2] = u - v;
w *= wlen;
}
}
}
if(inv){
// 逆变换需除以 n
for(cd& x : a) x /= n;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
vector<int> qs(T);
int mx = 0;
for(int i = 0; i < T; ++i){
cin >> qs[i];
mx = max(mx, qs[i]);
}
// 标记好数
vector<char> good(mx+1, 0);
for(int i = 1; i <= mx; ++i){
int x = i;
bool ok = true;
while(x){
int d = x % 10;
if(d == 2 || d == 4){
ok = false;
break;
}
x /= 10;
}
good[i] = ok;
}
// 计算 FFT 所需的长度
int lim = 1;
while(lim < 3*mx + 1) lim <<= 1;
vector<cd> A(lim), B(lim);
// A[i]=1 表示 f[i]=1,B[2*i]=1 表示 f[i]=1
for(int i = 0; i <= mx; ++i){
if(good[i]){
A[i] = 1;
B[2*i] = 1;
}
}
// FFT 变换
fft(A, false);
fft(B, false);
vector<long long> E1(mx+1), E2(mx+1);
// 计算 E1 = A*A*A
{
vector<cd> F = A;
for(int i = 0; i < lim; ++i) F[i] *= F[i] * A[i];
fft(F, true);
for(int i = 0; i <= mx; ++i)
E1[i] = llround(F[i].real());
}
// 计算 E2 = A*B
{
vector<cd> F = A;
for(int i = 0; i < lim; ++i) F[i] *= B[i];
fft(F, true);
for(int i = 0; i <= mx; ++i)
E2[i] = llround(F[i].real());
}
// 计算最终答案并输出
for(int n : qs){
long long e3 = (n % 3 == 0 && good[n/3]) ? 1 : 0;
long long tot = E1[n] - 3*E2[n] + 2*e3;
cout << (tot / 6) << "\n";
}
return 0;
}
Python
import sys
import threading
import numpy as np
def main():
input = sys.stdin.readline
T = int(input().strip())
qs = [int(input().strip()) for _ in range(T)]
mx = max(qs)
# 标记好数
good = np.zeros(mx+1, dtype=int)
for i in range(1, mx+1):
x = i
ok = True
while x > 0:
d = x % 10
if d == 2 or d == 4:
ok = False
break
x //= 10
good[i] = ok
# 构造多项式系数
lim = 1
while lim < 3*mx + 1:
lim <<= 1
A = np.zeros(lim, dtype=np.int64)
B = np.zeros(lim, dtype=np.int64)
A[:mx+1] = good
B[:2*mx+1:2] = good
# FFT 计算卷积
FA = np.fft.rfft(A)
FB = np.fft.rfft(B)
# E1 = A*A*A
E1 = np.fft.irfft(FA * FA * FA, n=lim).round().astype(np.int64)[:mx+1]
# E2 = A*B
E2 = np.fft.irfft(FA * FB, n=lim).round().astype(np.int64)[:mx+1]
# 计算答案
ans = [0] * (mx+1)
for n in range(mx+1):
e3 = 1 if n % 3 == 0 and good[n//3] == 1 else 0
tot = E1[n] - 3 * E2[n] + 2 * e3
ans[n] = tot // 6
# 输出结果
print("\n".join(str(ans[q]) for q in qs))
if __name__ == "__main__":
threading.Thread(target=main).start()
Java
import java.util.*;
public class Main {
// 定义复数类,用于存储和操作复数
static class Complex {
double real, imag;
Complex(double real, double imag) {
this.real = real;
this.imag = imag;
}
// 复数加法
Complex add(Complex other) {
return new Complex(this.real + other.real, this.imag + other.imag);
}
// 复数减法
Complex subtract(Complex other) {
return new Complex(this.real - other.real, this.imag - other.imag);
}
// 复数乘法
Complex multiply(Complex other) {
return new Complex(this.real * other.real - this.imag * other.imag,
this.imag * other.real + this.real * other.imag);
}
// 复数除法
Complex divide(double scalar) {
return new Complex(this.real / scalar, this.imag / scalar);
}
// 获取复数的实部
double real() {
return this.real;
}
}
// 自底向上的FFT变换
static void fft(List<Complex> a, boolean inv) {
int n = a.size(), j = 0;
// 位反转置换
for (int i = 1; i < n; ++i) {
int bit = n >> 1;
for (; (j & bit) != 0; bit >>= 1) j ^= bit;
j |= bit;
if (i < j) Collections.swap(a, i, j); // 交换元素
}
final double PI = Math.acos(-1);
// 迭代合并过程
for (int len = 2; len <= n; len <<= 1) {
double ang = 2 * PI / len * (inv ? -1 : 1); // 计算旋转因子角度
Complex wlen = new Complex(Math.cos(ang), Math.sin(ang)); // 计算旋转因子
for (int i = 0; i < n; i += len) {
Complex w = new Complex(1, 0);
// 逐步合并子问题
for (int k = 0; k < len / 2; ++k) { // 内部循环变量改名为 k
Complex u = a.get(i + k);
Complex v = a.get(i + k + len / 2).multiply(w);
a.set(i + k, u.add(v)); // 更新结果
a.set(i + k + len / 2, u.subtract(v)); // 更新结果
w = w.multiply(wlen); // 更新旋转因子
}
}
}
if (inv) {
// 如果是逆变换,需要除以n
for (int i = 0; i < n; ++i) {
a.set(i, a.get(i).divide(n));
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取测试案例数
int T = sc.nextInt();
int[] qs = new int[T];
int mx = 0;
// 读取每个测试案例
for (int i = 0; i < T; ++i) {
qs[i] = sc.nextInt();
mx = Math.max(mx, qs[i]); // 更新最大值
}
// 标记"好数",即不包含2或4的数字
boolean[] good = new boolean[mx + 1];
for (int i = 1; i <= mx; ++i) {
int x = i;
boolean ok = true;
while (x > 0) {
int d = x % 10;
if (d == 2 || d == 4) {
ok = false;
break;
}
x /= 10;
}
good[i] = ok; // 标记为好数
}
// 计算FFT需要的大小
int lim = 1;
while (lim < 3 * mx + 1) lim <<= 1;
// 创建FFT的输入数据
List<Complex> A = new ArrayList<>(Collections.nCopies(lim, new Complex(0, 0)));
List<Complex> B = new ArrayList<>(Collections.nCopies(lim, new Complex(0, 0)));
// 初始化A和B,A[i] = 1表示f[i] = 1,B[2 * i] = 1表示f[i] = 1
for (int i = 0; i <= mx; ++i) {
if (good[i]) {
A.set(i, new Complex(1, 0));
B.set(2 * i, new Complex(1, 0));
}
}
// 进行FFT变换
fft(A, false);
fft(B, false);
// 存储E1和E2的结果
long[] E1 = new long[mx + 1];
long[] E2 = new long[mx + 1];
// 计算 E1 = A * A * A
{
List<Complex> F = new ArrayList<>(A);
for (int i = 0; i < lim; ++i) {
F.set(i, F.get(i).multiply(F.get(i)).multiply(A.get(i)));
}
fft(F, true);
for (int i = 0; i <= mx; ++i) {
E1[i] = Math.round(F.get(i).real()); // 取实部并转换为long型
}
}
// 计算 E2 = A * B
{
List<Complex> F = new ArrayList<>(A);
for (int i = 0; i < lim; ++i) {
F.set(i, F.get(i).multiply(B.get(i)));
}
fft(F, true);
for (int i = 0; i <= mx; ++i) {
E2[i] = Math.round(F.get(i).real()); // 取实部并转换为long型
}
}
// 根据查询计算最终答案并输出
for (int n : qs) {
long e3 = (n % 3 == 0 && good[n / 3]) ? 1 : 0;
long tot = E1[n] - 3 * E2[n] + 2 * e3;
System.out.println(tot / 6); // 输出最终结果
}
sc.close(); // 关闭Scanner
}
}
题目内容
给定一个正整数n,请你求出满足以下条件的a,b,c三元组数量:
-
a+b+c=n。
-
a,b,c是不同的正整数。
-
a,b,c的各个数位均不包含数字2和4
注意,如果通过改变三个正整数的顺序可以得到相同的组合,则这样的组合也被视为同一种分解方法。例如,对于n=9,无论是1+3+5还是5+3+1,都只算作一种分解方法。
输入描述
每个测试文件均包含多组测试数据。第一行输入一个整数T(1≤T≤104)代表数据组数,每组测试数据描述如下:
在一行上输入一个整数n(1≤n≤106)代表初始数字
输出描述
对于每一组测试数据,在一行上输出一个整数,表示满足条件的三元组数量.
样例1
输入
2
9
18
输出
1
7
说明
对于n=9,只有1+3+5这一种解法