数字对

题目描述

小 H 是个善于思考的学生, 现在她又在思考一个有关序列的问题.

她的面前浮现出一个长度为 n 的序列 {ai}, 她想找出一段区间 [L, R] (\(1 \leq L \leq R \leq n\)) . 这个特殊区间满足, 存在一个 k (\(L \leq k \leq R\)) , 并且对于任意的 i (\(L \leq i \leq R\)) ,\(a_i\) 都能 被 ak 整除. 这样的一个特殊区间 [L, R] 价值为\(R - L\). 小 H 想知道序列中所有特殊区间的最大价值是多少, 而有多少个这样的区间呢? 这些 区间又分别是哪些呢? 你能帮助她吧.

输入格式

第一行, 一个整数\(n\).

第二行,\(n\) 个整数, 代表\(a_i\).

输出格式

第一行两个整数, num 和 val, 表示价值最大的特殊区间的个数以及最大价值.

第二行 num 个整数, 按升序输出每个价值最大的特殊区间的 L.

样例输入 1

5
4 6 9 3 6

样例输出 1

1 3
2

样例输入 2

5
2 3 5 7 11

样例输出 2

5 0
1 2 3 4 5

数据范围

30%:\(1 \leq n \leq 30\),\(1 \leq a_i \leq 32\).

60%:\(1 \leq n \leq 3000\),\(1 \leq a_i \leq 1024\).

80%:\(1 \leq n \leq 300000\),\(1 \leq a_i \leq 1048576\).

100%:\(1 \leq n \leq 500000\),\(1 \leq a_i < 2^{31}\).

Explanation

看起来是一道很高大上的题目. 最开始蒟蒻我想要这样写:

对所有的序列都可以遍历它们, 求得最大公约数看是否能够满足条件. 这个算法的复杂度应该 是 \(O(n^3)\) 的.

然后是不是能优化一下呢~ 于是我们就可以用线段树 (线段树在 gcd 函数上是有可合并性的) 来优化一下, 复杂度是 \(O(n^2 \times log(n))\).

接着说其实每一次记一下前面的状态就可以不必用数据结构, 直接水一个复杂度是 \(O(n^2)\) 的暴力搜索, 其实就可以得到 60 分解了.

随后我们需要将目标转向更高级的算法: 做法应该是要有复杂度 \(O(n \times log(n))\) 的. 这样就用上了 Sparse Table (一个不知道的东西, 之前别人在写这道题的时候我还在做 第一道题, 写了半天 Splay 没有过去, 所以没有看), 然后可以在一定时间内 AC.

刚才那个算法 @JerrySkywalker 已经过了, 但是这里还有一个玄学做法:

(先膜一发 @许益冰大神)

她是这样说的: 可以从左边扫一遍, 然后从右边扫一遍. 因为这些数有这样的性质: 如果能够 构成在 L 到 R 的区间内有至少一个最大公约数, 那么这个最大公约数一定是所有数之中最小的数 (即其他数都是它的倍数). 具体证明过程可以读者自行脑补.

然后确定了这些最小位置后打一些标记, 标记哪一些位置是最小的, 然后再扫一遍, 就是 一个很快的做法了.

大概证明了一下, 算法复杂度是 \(O(n)\) 的. @JerrySkywalker 坚持说还带一个指数, 但是 我表示 gcd 可以算作常数, 因为最大值固定 (手动滑稽). 但是回到家写了一个 Proof Of Concept 之后发现连个 gcd 的影子都没有调用到, 所以这个 log 也可以去掉了, 然后 就是一个纯正的 \(O(n)\)~

这种 玄学做法是可以 AC 的, 实测证明.

之后我有可能会补上一个 ST 表的代码 (或者不会, 要看心情).

Source Code


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;
typedef long long lli;
const int maxn = 500100;

int gcd(int x, int y) {
    if (y == 0) return x;
    return gcd(y, x % y); }

int n;
int arr[maxn], flag[maxn];
int dis[maxn][2];

int main(int argc, char** argv)
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &arr[i]);
    // Scan the entire array with this nice scanning utility.
    for (int i = 1; i <= n; ) {
        dis[i][0] = 0;
        int j = i + 1;
        for (; j <= n; j++) {
            if (arr[j] % arr[i] != 0)
                break;
            dis[i][0]++;
        }
        flag[i] = i;
        for (int k = i; k < j - 1; k++) {
            dis[k + 1][0] = dis[k][0] - 1;
            flag[k + 1] = i;
        }
        i = j;
    }
    for (int i = n; i >= 1; ) {
        dis[i][1] = 0;
        int j = i - 1;
        for (; j >= 1; j--) {
            if (arr[j] % arr[i] != 0)
                break;
            dis[i][1]++;
        }
        flag[i] = i;
        for (int k = i; k > j + 1; k--) {
            dis[k - 1][1] = dis[k][1] - 1;
            flag[k - 1] = i;
        }
        i = j;
    }
    // Done scanning, now just processing the results.
    vector<int> vec;
    int res = 0;
    for (int i = 1; i <= n; i++) {
        int tmp = dis[i][0] + dis[i][1];
        // printf("i %d tmp %d\n", i, tmp);
        // // Deprecated judgement method... (Overridden by code block lower)
        // if (i > 1 && (dis[i][0] < dis[i - 1][0] || dis[i][1] > dis[i + 1][1]))
        //     continue;
        if (i != flag[i])
            continue;
        if (tmp < res) {
            continue;
        } else if (tmp == res) {
            vec.push_back(i - dis[i][1]);
        } else { // tmp > res
            vec.clear();
            vec.push_back(i - dis[i][1]);
            res = tmp;
        }
    }
    printf("%d %d\n", int(vec.size()), res);
    sort(vec.begin(), vec.end());
    for (unsigned int i = 0; i < vec.size(); i++)
        printf("%d ", vec[i]);
    return 0;
}