CF2183E LCM is Legendary Counting Master
有益的题集第十篇。
推式子是一种享受折磨。
题意
你有一个长 \(n\) 的值域在 \([0,m]\) 的整数序列 \(a\)。称 \(a\) 是好的当且仅当它满足以下两个条件:
- \(a_1<a_2<...<a_n\)
- \(\frac{1}{\operatorname{lcm}(a_1,a_2)}+\frac{1}{\operatorname{lcm}(a_2,a_3)}+...+\frac{1}{\operatorname{lcm}(a_{n-1},a_n)}+\color{red}\frac{1}{\operatorname{lcm}(a_n,a_1)}\color{black}\ge 1\)
你要将 \(a\) 中所有为 \(0\) 的元素替换为 \([1,m]\) 中的数,并且要使它是好的,求有多少种满足条件的替换方式,答案对 \(998244353\) 取模。
解法
为了方便表述,令 \(a_{n+1}=a_1\)。
对于序列严格递增的条件,我们可以先不管,来看第二个条件。容易发现,\(\text{原式}=\sum\limits_{i=1}^{n}\frac{1}{\operatorname{lcm}(a_i,a_{i+1})}=\sum\limits_{i=1}^{n}\frac{\gcd(a_i,a_{i+1})}{a_ia_{i+1}}\)。由于当 \(a_i<a_{i+1}\) 时,\(\gcd(a_i,a_{i+1})\le a_{i+1}-a_i\),于是 \(\sum\limits_{i=1}^{n}\frac{\gcd(a_i,a_{i+1})}{a_ia_{i+1}}\le \sum\limits_{i=1}^{n}\frac{a_{i+1}-a_i}{a_ia_{i+1}}\)。
一个小学就学过的数学性质:若 \(a<b\),则 \(\frac{b-a}{ab}=\frac{1}{a}-\frac{1}{b}\),即裂项。所以
为什么求和上界是 \(n-1\) 呢,是因为 \(a_{n+1}=a_1<a_n\),不满足裂项条件。那么得到
显然 \(\gcd(a,b)\le\min\{a,b\}\),所以
哇,我们竟然得到了 \(\frac{1}{\operatorname{lcm}(a_1,a_2)}+\frac{1}{\operatorname{lcm}(a_2,a_3)}+...+\frac{1}{\operatorname{lcm}(a_{n-1},a_n)}+\frac{1}{\operatorname{lcm}(a_n,a_1)}\le1\)!而题目要求那一坨式子大于等于 \(1\),所以最终原式必须等于 \(1\)。
现在考虑什么时候那个式子能够取等。如果原式要等于 \(1\),则必须有 \(\boldsymbol{a_1=1}\),并且需要满足 \(\gcd(a_i,a_{i+1})=a_{i+1}-a_i\)。这也就是说,\(a_{i+1}-a_i\) 是 \(a_i\) 的因数,即存在正整数 \(d\) 使 \(d\mid a_i\) 且 \(a_{i+1}=a_i+d\)。百度一下可以发现,这样的二元组 \((a_i,a_{i+1})\) 不会超过 \(O(m\log m)\) 个。
考虑 dp。设 \(f_{i,j}\) 表示当前已经替换到了第 \(i\) 个数,将要换上去的数是 \(j\) 的总方案数。根据上面的推导,我们可以先预处理出在数据范围内的所有二元组 \((a_i,a_{i+1})\)。初始时 \(f_{1,1}=1\)。可以得到转移方程
其中 \(x\) 是能够与 \(j\) 组成那个二元组的所有数。
最终的答案为 \(\sum\limits_{i=1}^{m}f_{n,i}\)。
注意,因为有一些数是已经填好了的(即 \(a_i\not=0\)),所以这些数只能算它们自己本身的值的贡献,所以如果当前 \(j\not=a_i\) 的话要将这部分贡献清零。
记得取模。
Code
#include <bits/stdc++.h>
#define loop(i,a,b) for(int i=(a);~i;i=(b))
#define Mem(a,b) memset ((a),(b),sizeof((a)))
#define eb emplace_back
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N = 3000 + 15, mod = 998244353;
namespace FAST_IO {
//快读快写已为您省略
}
using namespace FAST_IO;
bool MS;
int n, m;
int a[N];
ll dp[N][N];
vector <int> d[N];
void solve () {
read (n, m);
for (int i = 1; i <= n; ++ i) read (a[i]);
if (a[1] > 1) {
print (0, '\n');
return ;
}//a1=1才能进行计算,因为这是前提
dp[1][1] = 1;
for (int i = 2; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
dp[i][j] = 0;
for (int x : d[j]) (dp[i][j] += dp[i - 1][j - x]) %= mod;
}//计算贡献
if (a[i]) for (int j = 1; j <= m; ++ j) if (j ^ a[i]) dp[i][j] = 0;//去掉不合法的贡献
}//转移
ll ans = 0;
for (int j = 1; j <= m; ++ j) {
(ans += dp[n][j]) %= mod;
}
print (ans, '\n');
}
bool MT;
int main () {
for (int i = 1; i <= N - 15; ++ i) {
for (int j = i; j <= N - 15; j += i) {
d[j].eb (i);
}
}//预处理
int T_T;
read (T_T);
while (T_T --) solve ();
cerr << "Memory:" << (&MT - &MS) / 1048576.0 << "MB Time:" << clock() << "ms\n";
return 0;
}

浙公网安备 33010602011771号