[BJWC2010] 取数游戏
传送门
题目描述
小 C 刚学了辗转相除法,正不亦乐乎,这小 P 又出来捣乱,给小 C 留了个 难题。 给 N N N 个数,用 a 1 , a 2 , ⋯ , a n a_1,a_2, \cdots ,a_n a1,a2,⋯,an来表示。现在小 P 让小 C 依次取数,第一个数可以 随意取。假使目前取得 a j a_j aj,下一个数取 a k ( k > j ) a_k(k>j) ak(k>j),则 a k a_k ak必须满足 g c d ( a j , a k ) ≥ L \mathrm{gcd}(a_j,a_k)≥L gcd(aj,ak)≥L。
到底要取多少个数呢?自然是越多越好! 不用多说,这不仅是给小 C 的难题,也是给你的难题。
输入格式
第一行包含两个数 N N N 和 L L L。 接下来一行,有 N N N 个数用空格隔开,依次是 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots ,a_n a1,a2,⋯,an。
输出格式
仅包含一行一个数,表示按上述取法,最多可以取的数的个数。
样例 #1
样例输入 #1
5 6
7 16 9 24 6
样例输出 #1
3
提示
样例解释
选取 3 3 3个数 16 , 24 , 6 16,24,6 16,24,6。 g c d ( 16 , 24 ) = 8 \mathrm{gcd}(16,24)=8 gcd(16,24)=8, g c d ( 6 , 24 ) = 6 \mathrm{gcd}(6,24)=6 gcd(6,24)=6。
数据范围
30% 的数据
N
≤
1000
N≤1000
N≤1000;
100% 的数据
N
≤
50000
,
2
≤
L
≤
a
i
≤
1000000
N≤50 000,2≤L≤a_i≤1 000 000
N≤50000,2≤L≤ai≤1000000。
解题思路
根据题意,我们不难列出动态转移方程: f i = max f j + 1 ( 1 ≤ j < i , gcd ( a i , a j ) ≥ L ) f_i=\max{f_j+1}(1\leq j <i,\gcd(a_i,a_j)\geq L) fi=maxfj+1(1≤j<i,gcd(ai,aj)≥L) 。但是,时间复杂度为 O ( n 2 ) O(n^2) O(n2) 无法通过。
再细品,会发现 gcd ( a i , a j ) \gcd(a_i,a_j) gcd(ai,aj) 可以用桶记录, t o n g x tong_x tongx 表示因数 x x x 最近一次出现的位置,那么方程式可以写成 f i = max ( f t o n g x + 1 ) ( x ∣ a i ) f_i=\max(f_{tong_x}+1)(x|a_i) fi=max(ftongx+1)(x∣ai) ,则最终答案 A n s = max ( f i ) Ans=\max(f_i) Ans=max(fi)。
AC Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int Maxn = 1000000 + 5;
int N, L, a[Maxn];
int tong[Maxn], f[Maxn];
int ans;
inline void work() {
cin >> N >> L;
for (int i = 1; i <= N; i++) {
cin >> a[i];
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j * j <= a[i]; j++) {
if (a[i] % j == 0) {
int tmp1 = j, tmp2 = a[i] / j;
if (tmp1 >= L) {
f[i] = max(f[i], f[tong[tmp1]] + 1);
tong[tmp1] = i;
}
if (tmp1 != tmp2) {
f[i] = max(f[i], f[tong[tmp2]] + 1);
tong[tmp2] = i;
}
}
}
}
for (int i = 1; i <= N; i++) {
ans = max(ans, f[i]);
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
work();
return 0;
}