题意
链接: 题目.
给定一个长度为n的数组,下标从1到n,其中an和a1相连
每轮操作得到一个新的数组b,对于所有的i∈[1,n],b[i]=gcd(a[i],a[(i+1)%n]).最后将数组b复制给a
问,执行上述操作多少轮之后,a数组中的所有数字都相同
思路
最终的结果有多种可能
两个数取gcd,相当于保留两个数的公共因子,对于整个序列执行无数次gcd操作之后,等价于序列中每个数变成原序列中所有公共因子的乘积,我们最终的结果可能是1到gcd(a[1],a[n])之间的因子,我们不妨执行a[i]/=gcd(a[1],a[n]),这样结果就是唯一的1,
现在的目的就是执行多少次操作之后数组全部变成一,
对于每一个位置的数是与下一位置的数进行gcd操作,如果两个数字本身互质,则操作一次就能得到1,
如果不互质,假设存在公因子2,组此轮变化后,a[i]=2,a[i+1]根据a[i+2]来决定,a[i+2]由,,,,,
当a[i+1]操作一次之后不含有因子2,则表明a[i+2]也不含因子2,因此两轮之后就可以将a[i]变成1
当a[i+1]操作一次之后存在因子2,则表明a[i+2]也存在因子2,此时我们假设a[i+2]的情况
我们观察中得出,如果a[i], a[i + 1], …, a[i + k]都存在公因子2, a[k + 1]不存在因子2,我们需要进行k轮操作之后,才能将a[i]变成1
对于本位置,我们把所有的位置取max。
线段树+树外二分:
我们用线段树维护每个区间静态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;
}
链接: 转载.