题目链接: Array Stabilization (GCD version)
大致题意
给定一个长度为n的数组, 下标从1~n. 其中an和a1相连(成环).
每轮操作得到一个新的数组b: 对于所有的i∈[1, n], b[i] = gcd(a[i], a[i + 1])
(b[n] = gcd(a[n], a[1])
). 最后把新数组b复制给原数组a.
问: 执行完多少轮操作后, a数组中的所有数字都相同.
解题思路
考虑到最终a数组, 会有很多种相等的可能情况. 这不便于我们接下来的思考.
两个数取gcd, 相当于保留两个数的公共质因子. 对于整个序列执行无数次gcd操作后, 等价于序列中每个数字变为原序列中所有公共质因子的乘积 . 因此我们不妨执行 a[] /= gcd(a[]). 此时我们最终得到的序列一定是全1序列.
现在我们的目的就很明确了: 求执行完多少次操作后, 使得序列中每一个数字都等于1.
考虑到是每次是i位置的数字(假设a[i] != 1), 与i+1位置的数字进行gcd操作, 如果两个数字本身互质, 则操作1次就可以使得i位置数字变为1.
若不互质, 不妨假设存在公因子2, 则此轮变化后, a[i] = 2, a[i+1]我们需要根据a[i+2]来确定, 而a[i+2]的情况需要根据…… 我们考虑对a[i+1]一轮操作后的结果分情况考虑:
情况①: 如果操作后a[i+1]中不含有因子2, 则表明a[i+2]中也不含有因子2, 此时操作两轮可以把a[i]变为1.
情况②: 如果操作后a[i+1]中存在因子2, 则表明a[i+2]中也存在因子2, 此时我们又需要假设a[i+2]的情况, 而情况情况是同于假设a[i+1]的.
我们观察可以发现, 如果a[i], a[i + 1], …, a[i + k]都存在公因子2, a[k + 1]不存在因子2, 则我们需要k轮操作, 才能把a[i]变为1.
得出结论: 对于每一个位置i, 我们开始连续取gcd, 当第一次出现 gcd(a[i], a[i + 1], …, a[i + k]) == 1
时, 表示i位置需要操作k轮才能变为1.
那么对于本题, 我们把所有位置的k取max即为最终答案.
我们怎么得到每个位置的k呢? 线段树 + 树外二分 DS选手只能想到暴力线段树
我们用线段树维护每个区间静态gcd的信息. 对于每个位置查询最靠左gcd == 1的位置即可.
这里还有一点点细节问题: 由于a[n]后面是a[1], 题目中的数组是个环, 我们可以通过把数组复制一遍的方式来模拟环. 即: a[n + 1] = a[1], a[n + 2] = a[2], …, a[n + n] = a[n].
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 4E5 + 10; //记得开2倍
int a[N];
struct node {
int l, r;
int val;
}t[N << 2];
void pushup(int x) { t[x].val = gcd(t[x << 1].val, t[x << 1 | 1].val); }
void build(int l, int r, int x = 1) {
t[x] = { l, r, a[l] };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
pushup(x);
}
int ask(int l, int r, int x = 1) { //查询[l, r]的gcd
if (l <= t[x].l and r >= t[x].r) return t[x].val;
int mid = t[x].l + t[x].r >> 1;
int res = 0;
if (l <= mid) res = ask(l, r, x << 1);
if (r > mid) res = gcd(res, ask(l, r, x << 1 | 1));
return res;
}
int main()
{
int t; cin >> t;
while (t--) {
int n; scanf("%d", &n);
int d = 0; //得到gcd(a[]).
rep(i, n) scanf("%d", &a[i]), d = gcd(d, a[i]);
if (d != 1) rep(i, n) a[i] /= d;
rep(i, n) a[n + i] = a[i];
n <<= 1;
build(1, n);
int res = 0;
rep(i, n / 2) {
if (a[i] == 1) continue;
int l = i + 1, r = n; //右端点的取值区间
while (l < r) {
int mid = l + r >> 1;
int cou = ask(i, mid);
if (cou == 1) r = mid;
else l = mid + 1;
}
res = max(res, r - i);
}
printf("%d\n", res);
}
return 0;
}