传送门: https://zoj.pintia.cn/problem-sets/91827364500/problems/91827370222
题意
给 一 个 n 和 一 个 m , 表 示 你 可 以 重 复 用 [ 1 , n ] 里 的 数 字 。 给一个n和一个m,表示你可以重复用[1,n]里的数字。 给一个n和一个m,表示你可以重复用[1,n]里的数字。
构 造 一 个 数 列 , 数 列 中 前 一 个 数 字 必 须 是 后 一 个 数 字 的 因 子 , 即 a i ∣ a i + 1 。 构造一个数列,数列中前一个数字必须是后一个数字的因子,即a_i|a_{i+1}。 构造一个数列,数列中前一个数字必须是后一个数字的因子,即ai∣ai+1。
问 能 构 造 多 少 个 这 样 的 数 列 。 问能构造多少个这样的数列。 问能构造多少个这样的数列。
思路
比 如 n = 8 , m = 3. 可 以 构 造 : 比如n=8,m=3.可以构造: 比如n=8,m=3.可以构造:
8
8
8
8\;8\;8
888
8
8
4
8\;8\;4
884
8
4
4
8\;4\;4
844
4
4
4
4\;4\;4
444
.
.
.
...
...
假 如 我 们 之 后 一 个 数 后 面 可 以 跟 多 少 因 子 , 然 后 这 个 因 子 后 面 . . . 假如我们之后一个数后面可以跟多少因子,然后这个因子后面... 假如我们之后一个数后面可以跟多少因子,然后这个因子后面...
这 就 是 个 递 推 啊 ! 这就是个递推啊! 这就是个递推啊!
设 f [ i ] [ j ] 表 示 以 i 结 尾 , 长 度 为 j 的 序 列 个 数 。 设f[i][j]表示以i结尾,长度为j的序列个数。 设f[i][j]表示以i结尾,长度为j的序列个数。
因 为 必 须 是 因 子 , 所 以 转 移 方 程 为 : 因为必须是因子,所以转移方程为: 因为必须是因子,所以转移方程为:
f [ i ] [ j ] + = f [ k ] [ j − 1 ] ( k 为 i 的 因 子 ) f[i][j]+=f[k][j-1](k为i的因子) f[i][j]+=f[k][j−1](k为i的因子)
所 以 要 先 预 处 理 所 有 数 字 的 因 子 , 然 后 进 行 d p 即 可 。 a n s = ∑ i = 1 n f [ i ] [ m ] 。 所以要先预处理所有数字的因子,然后进行dp即可。ans=\sum_{i=1}^nf[i][m]。 所以要先预处理所有数字的因子,然后进行dp即可。ans=∑i=1nf[i][m]。
Code
#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll quick_pow(ll a, ll b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a;
a = a * a;
b >>= 1;
}
return ans;
}
const int N = 2002;
ll f[N][N]; // 长度为j,以i结尾的个数
vector<int> p[N];
void init() {
// p[1].emplace_back(1);
for(int i = 1;i < N; i++) {
p[i].emplace_back(1);
for(int j = 2;j <= i; j++) {
if(i % j == 0) p[i].emplace_back(j);
}
}
}
void solve() {
init();
for(int i = 1;i < N; i++) {
f[i][1] = 1;
for(auto k : p[i]) {
for(int j = 2;j < N; j++) {
f[i][j] = (f[i][j] + f[k][j - 1]) % mod;
}
}
}
int _; cin >> _;
while(_--) {
int n, m; cin >> n >> m;
ll ans = 0;
for(int i = 1;i <= n; i++) {
ans = (ans + f[i][m]) % mod;
}
cout << ans << endl;
}
}
signed main() {
solve();
}