【题解】CF1547F Array Stabilization (GCD version)
原题链接: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 \times 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=\prod\limits_{i} p_i^{A_i}$,$b=\prod\limits_{i} p_i^{B_i}$,$c=\prod\limits_{i} p_i^{C_i}$,其中 $A_i,B_i,C_i$ 均为非负整数。
那么可以进行推导。
$$
\begin{aligned}
右式 &= \gcd\left(\gcd\left({\prod\limits_{i} p_i^{A_i},\prod\limits_{i} p_i^{B_i}}\right),c\right) \\
&= \gcd \left(\prod\limits_{i} p_i^{\min(A_i,B_i)},c\right)\\
&= \gcd \left(\prod\limits_{i} p_i^{\min(A_i,B_i)},\prod\limits_{i} p_i^{C_i}\right) \\
&= \prod\limits_{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})=\gcd\left(\gcd(a_i,a_{i+1}),\gcd(a_{i+1},a_{i+2})\right)=\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\n", ans); } return 0; }