返回顶部

SCUT - 86 - 加农! = 数学常识 + 栈

https://scut.online/p/86

题意:给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\n", minlen);
    else
        printf("-1");
}
posted @ 2019-10-17 18:42  Inko  阅读(183)  评论(0编辑  收藏  举报