原题链接:https://codeforces.ml/contest/1547/problem/F
题意
有 $n$ 个数 $a_1,a_2,...,a_n$(这里略有改动,原题是下标为 $0 sim n-1$,此处为方便变成 $1 sim n$)。
每次新设立一个数组 $b$,且 $b_i = gcd(a_i,a_{i+1})$,特别地 $b_n=gcd(a_n,a_1)$,然后将序列 $b$ 的值都赋值给 $a$。
问需要操作多少次,使得 $a_1=a_2=...=a_n$。
$n le 2 imes 10^5$,$1 le a_i le 10^6$。
分析
首先我们需要知道一个结论:$gcd(a,b,c)=gcd(gcd(a,b),c)$。
简略证明如下:
可以将 $a,b,c$ 质因数分解。
设 $p_i$ 为从小到大第 $i$ 个质数,$a=prodlimits_{i} p_i^{A_i}$,$b=prodlimits_{i} p_i^{B_i}$,$c=prodlimits_{i} p_i^{C_i}$,其中 $A_i,B_i,C_i$ 均为非负整数。
那么可以进行推导。
$$
egin{aligned}
右式 &= gcdleft(gcdleft({prodlimits_{i} p_i^{A_i},prodlimits_{i} p_i^{B_i}}
ight),c
ight) \
&= gcd left(prodlimits_{i} p_i^{min(A_i,B_i)},c
ight)\
&= gcd left(prodlimits_{i} p_i^{min(A_i,B_i)},prodlimits_{i} p_i^{C_i}
ight) \
&= prodlimits_{i} p_i^{min(A_i,B_i,C_i)} \
&= 左式 \
end{aligned}
$$
断环成链
我们发现由于首尾将序列 $b$ 的构造方式是把序列 $a$ 在环上求,所以为了方便,我们将 $a$ 复制,接到原来的 $a$ 尾部。
继续分析
如果我们是第一次操作,那么 $b_i=gcd(a_i,a_{i+1})$,此时我们暂时不将 $b$ 赋给 $a$,先拿数组 $c$ 保存。
第二次操作,有 $b_i=gcd(c_i,c_{i+1})=gcdleft(gcd(a_i,a_{i+1}),gcd(a_{i+1},a_{i+2}) ight)=gcd(a_i,a_{i+1},a_{i+2})$。
然后就可以发现第 $k$ 次操作有 $b_i=gcd{a_i,a_{i+1},a_{i+2},...,a_{i+k}}$,这一行 $a$ 是初始序列。
如果想知道要几次操作使得 $a_1,a_2,...,a_{n}$ 相等,相当于求需要几次操作,使得所有的 $b_i$ 均等于 $gcd(a_1,a_2,...,a_n)$。
那么如果这个次数不够,那么至少存在一个 $b_i$ 的值不等于(即大于) $gcd(a_1,a_2,...,a_n)$。
综上所述,我们需要求一个最小的 $k$,使得存在一个满足 $1 le i le n$ 的 $i$,有 $gcd(a_i,a_{i+1},...,a_{i+k-1})=gcd(a_1,a_2,...,a_n)$,此时答案即为 $k-1$。
算法选择
如果我们对每个位置 $i$ 按顺序暴力寻找 $k$,那复杂度为 $n^2 log max{a_i}$ 的,显然不行。
发现,如果满足 $gcd(a_i,a_{i+1},...,a_{i+k-2})=gcd(a_1,a_2,...,a_n)$,那么一定有 $gcd(a_i,a_{i+1},...,a_{i+k-1})=gcd(a_1,a_2,...,a_n)$。
即存在一个 $j$,若 $k le j$ 其 $gcd$ 的值不等于 $gcd(a_1,a_2,...,a_n)$,反之 $k>j$ 时二者相等。
所以,对于每个 $i$ 我们可以二分查找最小的 $k$。
最后还存在一个问题,就是逐步求 $gcd(a_i,a_{i+1},a_{i+2}...)$ 这步会拖慢程序的效率。
观察到原始的 $a_i$ 不会改变,且多次给同一个 $a_i$ 求 $gcd$ 不影响结果,我们可以使用 ST 表。
先对已经破环的链存入 ST 表进行预处理,在查询的时候只需要 $O(log max{a_i})$ 的复杂度(用于求 $gcd$)。
这个做法时间复杂度为 $mathcal{O}(n log n log max{a_i})$,在 $4$ 秒的限制下可以轻松通过。
代码
#include <bits/stdc++.h> #define INF 1e9 #define eps 1e-6 typedef long long ll; using namespace std; int t, n, a[400010], GCD, ans, st[400010][20]; bool b[400010], bb; int gcd(int x, int y){ if(!y) return x; return gcd(y, x % y); } int query(int i, int j){ // ST 表查询 int k = log2(j - i + 1); return gcd(st[i][k], st[j - (1 << k) + 1][k]); } int main(){ cin >> t; while(t--){ cin >> n, ans = bb = 0; for(int i = 1; i <= n; i++) cin >> a[i], b[i] = 0; for(int i = 1; i <= n; i++) a[i + n] = a[i], st[i][0] = st[i + n][0] = a[i]; // 破环为链 for(int i = 1; i <= n; i++) if(a[i] != a[1]){ bb = 1; break; } if(!bb){ puts("0"); continue; } GCD = a[1]; for(int i = 2; i <= n; i++) GCD = gcd(GCD, a[i]); for(int j = 1; (1 << j) <= n + n; j++) // 注意这里是 n + n,ST 表需要处理整条链 for(int i = 1; i + (1 << j) - 1 <= n + n; i++) st[i][j] = gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); for(int i = 1, L, R, mid; i <= n; i++){ L = i, R = n + i - 1; while(L < R){ int mid = (L + R) >> 1; if(query(i, mid) != GCD) L = mid + 1; else R = mid; } ans = max(ans, L - i); } printf("%d ", ans); } return 0; }