题意:给5e6个数,选一段最小的[l,r],使得其中的数线性组合可以取遍整数。
显然当且仅当gcd为1的时候就可以遍历所有整数。
问题变成怎么求一段连续区间的gcd,可以用队列来尺取。
但是弹出元素的时候没办法记录逆操作,所以可以用两个栈实现一个队列,只需要满足结合律(像之前网络赛矩阵乘法那题)。
另一种办法是分治,solve(l,r)表示[l,r]的gcd,顺带更新[l,r]内的gcd为1的最短长度,那么只需要解决跨越中点的区间,记录中点向左推进的各次gcd,然后枚举右端点,每个右端点在左边的gcd中二分出一个最近的距离。这个复杂度感觉爆炸。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5e6;
stack<int> st1, st2;
int g, x[MAXN + 5];
int sumsiz = 0;
void push(int x) {
if(g == -1) {
st1.push(x);
g = x;
} else {
st1.push(x);
g = __gcd(g, x);
}
++sumsiz;
}
void pop() {
if(st2.size()) {
st2.pop();
} else {
if(st1.empty())
exit(-1);
int tg = st1.top();
while(st1.size()) {
st2.push(tg);
st1.pop();
if(st1.size())
tg = __gcd(tg, st1.top());
}
g = -1;
st2.pop();
}
--sumsiz;
}
int sumg() {
if(sumsiz == 0)
return -1;
if(st2.size()) {
if(g != -1)
return __gcd(g, st2.top());
else
return st2.top();
} else
return g;
}
int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
#endif // Yinku
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &x[i]);
}
bool suc = 0;
int minlen = MAXN;
g = -1;
for(int i = 1; i <= n; ++i) {
push(x[i]);
while(sumg() == 1) {
suc = 1;
minlen = min(minlen, sumsiz);
pop();
}
}
if(suc)
printf("%d
", minlen);
else
printf("-1");
}