*题解:P13568 [CCPC 2024 重庆站] 乘积,欧拉函数,求和

题目链接

解析

首先,我们有公式:

\[\varphi(x) = x\prod_{p \mid x}\frac{p - 1}{p} \]

其中 \(p\) 为质数。

于是就有两种思路,定 \(x\) 求 \(\prod_{p \mid x}(1 - \frac{1}{p})\) 之和与定质因子求 \(x\) 之和。前者发现行不通,考虑后者。考虑状压,设 \(f_{i,S}\) 表示考虑了前 \(i\) 个数,已有的质因子集合为 \(S\) 时的 \(x\) 之和。但是由于值域 $ V = 3000$ 以内的质数很多所以这个做法仍然倒闭。但是我们发现,对于任意一个 \(a_i\),大于 \(\sqrt{a_i}\) 的质因子至多只有一个,而 \(\le \sqrt{V}\) 的质数只有 \(16\) 个,故尝试把大于 \(\sqrt{V}\) 的质因子单独摘出来。

具体地,记 \(S_x\) 为 \(x\) 的质因子集合,则普通的转移方程是这样的:

\[f_{i,S} = f_{i - 1,S} + \sum_{T\cup S_{a_i}= S} f_{i - 1,T}\cdot a_i \]

现在要考虑大于 \(\sqrt{V}\) 的质因子 \(p_i\),那么就先将所有 \(a_i\) 按 \(p_i\) 排序。既然 \(p_i\) 没法存到状态里,那就把它的贡献乘到值里去。故更改状态定义为 \(f_{i,0/1,S}\) 表示考虑了前 \(i\) 个数,在前面没有 / 有选以 \(p_i\) 为质因子的数,选取了的 \(\le \sqrt{V}\) 的质因子集合为 \(S\) 时,\(x\) 与选取了的 \(>\sqrt{V}\) 的质因子的所有组合的乘积之和。那么有:

\[f_{i,0,S} = f_{i - 1,0,S} + f_{i - 1,1,S}[p_i\not=p_{i - 1}]\\ \]

此外,当 \(p_i = p_{i - 1}\) 时,有:

\[f_{i,1,S} = f_{i - 1,1,S}+ \sum_{T\cup S_{a_i} = S}(f_{i - 1,1,T} + f_{i - 1,0,T}\cdot \frac{p_{i} - 1}{p_i})\cdot a_{i} \]

否则有:

\[f_{i,1,S} = \sum_{T\cup S_{a_i} = S}(f_{i - 1,1,T} + f_{i - 1,0,T})\cdot a_{i} \cdot \frac{p_{i} - 1}{p_i} \]

直接转移时间复杂度 \(O(2^{2t}n)\),其中 \(t\) 为 \(\le \sqrt V\) 的质数个数,无法通过。但我们发现 \(\lvert S_{a_i}\rvert\) 很小,因为 \(2 \times 3\times 5 \times 7 \times 11\times 13 = 30030 > V\),故 \(\le V\) 的数不同质因子个数至多有 \(5\) 个,借此可以缩小 \(T\) 的可选范围。但还是无法通过,究其根本是因为我们在尝试求有哪些集合 \(T\) 对 \(S_{a_i}\) 取并后会变成 \(S\),而取并没有逆运算,导致我们只能枚举。正难则反,不妨考虑刷表法,即看当前状态可以转移到哪些状态,这样做的转移方程为:

\[f_{i,0,S} + f_{i + 1,0,S} \to f_{i + 1,0,S}\\ f_{i,0,S}\cdot \frac{p_{i + 1} - 1}{p_{i + 1}} \cdot a_{i + 1} + f_{i + 1,1,S \cup S_{a_{i+ 1}}}\to f_{i + 1,1,S\cup S_{a_i+ 1}}\\ \]

此外,若 \(p_i \not= p_{i + 1}\),则有:

\[f_{i,1,S} + f_{i + 1,0,S} \to f_{i + 1,0,S} \\ f_{i,1,S} \cdot \frac{p_{i + 1} - 1}{p_{i + 1}} \cdot a_{i + 1} + f_{i + 1,1,S \cup S_{a_{i+ 1}}}\to f_{i + 1,1,S\cup S_{a_{i + 1}}} \]

否则有:

\[ f_{i,1,S} + f_{i + 1,1,S} \to f_{i + 1,1,S}\\ f_{i,1,S} \cdot a_{i + 1} + f_{i + 1,1,S \cup S_{a_{i + 1}}}\to f_{i + 1,1,S\cup S_{a_{i + 1}}} \]

时间复杂度 \(O(2^tn)\)。

想到了的地方 / 思考过的做法

线性筛求 \(\varphi\)。

没想到的地方

利用公式拆贡献。

为什么没想到 / 如何想到

认为 \(x\) 太大,无法利用公式根号复杂度求 \(\varphi\),没有把式子实际列出来观察。

代码

前面的注释是自己做题时的思路,在此题中跟正解无关。

/*
f[i] 表示前 i 个数 - 1 组成的集合的子集元素乘积之和
f[i] = f[i - 1] + f[i - 1] * (a[i] - 1) 
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 2000,M = 16 + 2,V = 3000,mod = 998244353;
int f[2][2][(1 << M) + 5];
int mul[(1 << M) + 5];
int a[N],s[N],p[N],val[V + 5];
struct S{
	int p,s,a;
	friend bool operator < (S a,S b){
		return a.p < b.p;
	}
};
int qmi(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = 1ll * res * a % mod;
		b >>= 1;
		a = 1ll * a * a % mod;
	}
	return res;
}
int inv(int x){
	return qmi(x,mod - 2);
}
int get(int p){
	return val[p];
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("math.in","r",stdin);
//	freopen("math.out","w",stdout);
	vector<int> pri;
	for(int i=2;i * i <= V;i++){
		bool flag = true;
		for(int j=2;j * j <= i;j++){
			if(i % j == 0){
				flag = false;
				break;
			}
		}
		if(flag){
			pri.push_back(i);
		}
	} 
	val[1] = 1;
	for(int i=2;i<=V;i++){
		val[i] = 1ll * (i - 1) * inv(i) % mod;
	}
	int n;
	cin>>n;
	vector<S> vec;
	vec.push_back({-1,-1,-1});
	for(int i=1;i<=n;i++){
		cin>>a[i];
		int aa = a[i];
		for(int j=0;j<pri.size() && pri[j] <= a[i];j++){
			while(a[i] % pri[j] == 0){
				s[i] |= (1 << j);
				a[i] /= pri[j];
			}
		}
		if(a[i] * a[i] > 3000){
			p[i] = a[i];
		}else{
			p[i] = 1;
			int pos = lower_bound(pri.begin(),pri.end(),a[i]) - pri.begin();
			if(a[i] != 1)
				s[i] |= (1 << pos);
		}
		vec.push_back({p[i],s[i],aa});
	}
	for(int i=0;i<(1 << 16);i++){
		mul[i] = 1;
		for(int j=0;j<16;j++){
			if(i & (1 << j)){
				mul[i] = 1ll * mul[i] * get(pri[j]) % mod;
			}
		}
	}
	sort(vec.begin(),vec.end());
	f[0][0][0] = 1;
	for(int i=0;i<n;i++){
		int b = i & 1;
		int p = vec[i + 1].p,s = vec[i + 1].s,a = vec[i + 1].a,lp = vec[i].p;
		for(int j=0;j<(1 << 16);j++){
			bool x = p != 1;
			f[b ^ 1][0][j] = (f[b ^ 1][0][j] + f[b][0][j]) % mod;
			f[b ^ 1][x][j | s] = (f[b ^ 1][x][j | s] + 1ll * f[b][0][j] * get(p) % mod * a % mod) % mod; 
			if(p != lp){
				f[b ^ 1][0][j] = (f[b ^ 1][0][j] + f[b][1][j]) % mod;
				f[b ^ 1][x][j | s] = (f[b ^ 1][x][j | s] + 1ll * f[b][1][j] * get(p) % mod * a % mod) % mod;
			}
			if(p == lp){
				f[b ^ 1][1][j] = (f[b ^ 1][1][j] + f[b][1][j]) % mod;
				f[b ^ 1][x][j | s] = (f[b ^ 1][x][j | s] + 1ll * f[b][1][j] * a % mod) % mod;
			}
			
		}
		for(int j=0;j<(1 << 16);j++){
			f[b][0][j] = f[b][1][j] = 0;
		}
	}
	int res = 0;
	for(int i=0;i<(1 << 16);i++){
		int b = n & 1;
		res = (res + 1ll * mul[i] * f[n & 1][0][i] % mod + 1ll * mul[i] * f[n & 1][1][i] % mod) % mod;
	}
	cout<<res;
	return 0;
}
posted @ 2026-10-01 20:47  yutar  阅读(3)  评论(0)    收藏  举报