代码源:808、最长同余子数组

368 阅读4分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路 logo.png

题目描述

这是4月20日代码源div2的每日一题。

最长同余子数组 - 题目 - Daimayuan Online Judge

给定一个 N 长数组 {A}, 元素之间 两两不同.

现在要求你找出最长的 连续子序列, 使得这些元素 (modM) 意义下同余, 其中 M≥2.

形式化的说, 在数组中找到最长的 A[L..R],∃M≥2, 使得:

AL≡AL+1≡AL+2≡⋯≡AR(modM) 其中, a≡b(modM) 即是说 a%M=b%M 输出此长度即可.

数据规模

1≤n≤2×10^3 1≤ai≤10^18 输入格式 第一行一个数字 N。

接下来一行 N 个整数 A1,A2,…,AN。

输出格式

一个数,表示最长连续同余子序列的长度。

样例输入

4 8 2 10 5

样例输出

3 注意到 8,2,10 均为偶数.

bonus1: consider what if N is greater (even 1≤N≤2×105)?

bonus2: consider how to solve the 'subsequence' version.

问题解析

这题如果数据是1e3的话完全可以暴力求解。

首先,怎么知道一个数组是否同余呢?当然是a1%m==a2%m==……==an%m。换句话说,他们的之间的差值,是可以被一个数整除的。

比如1 5 7,差值是4 2,可以被2整除,这就是个同余数组了。这属于是一个结论记住就行。

那么我们可不可以这么想,我们先把所有数的相邻差值算出来,然后看有多长的差值数组都能被一个数整除,那就可以知道最长连续同余子序列的长度了。你问咋知道能不能被一个数整除?那当然是计算他们的最大公约数了啊,不过要注意,这个最大公约数不能是1(题目要求大于等于2),然后此时问题终于被我转换成了:找一个最长的差值数组,他们的最大公约数大于1。

我们可以滑动窗口跑差值数组,每次计算当前滑动窗口的gcd,如果大于1,则右端向前走,反之左端向前走。我们在这过程记录滑动窗口的最大长度就行。每次算一下滑动窗口内部的gcd,最后算下来总过程复杂度是n^2logn,是可以过的。不过有一个要注意的点,用我这个方法会有个问题,当我们的相邻值相等时,当然也该算同余,但是相等就说明差值为0,那么gcd当然不可能大于1,所以相等的这一段可能被我们忽视掉,我们应该加上一个回头的过程,当计算完一个区间的gcd后,我们回头计算一下上一个元素和滑动窗口里的元素是否模去gcd后相等,如果相等那么这个元素也该算在同于子序列里,我们就这么计算,算到模去gcd后不相等即可。

要写就写难的,写题不就是锻炼自己。

2e5数据的情况下显然是不可以过的,那么我们该怎么优化呢?滑动窗口的n复杂度肯定不能优化掉了,那我们就从计算区间的gcd入手,怎么把计算区间的gcd从nlogn降下来?说到区间查询问题。当然是我们的线段树了,只要准备一个线段树,每个节点存的是当前区间的gcd即可,然后我们每次查询区间的gcd时间复杂度也才logn,这样总共过程就是nlogn了,2e5也能过。

AC代码

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 100010;
ll a[N], f[4 * N];

ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b, a % b);
}

void buildtree(ll k, ll l, ll r)
{
    if (l == r)
    {
        f[k] = a[l];
        return;
    }
    int m = (l + r) / 2;
    buildtree(k + k, l, m);
    buildtree(k + k + 1, m + 1, r);
    f[k] = gcd(f[k + k], f[k + k + 1]);
}

ll calc(ll k, ll l, ll r, ll x, ll y)
{
    if (l == x&&r==y)return f[k];

    int m = (l + r) / 2;
    if (y <= m)return calc(k + k, l, m, x, y);
    else
        if (x > m)return calc(k + k + 1, m + 1, r, x, y);
        else
        {
            return gcd(calc(k + k, l, m, x, m), calc(k + k + 1, m + 1, r, m + 1, y));
        }
}

int main()
{
    cin.sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, t;
    cin >> n;
    vector<ll>v(n + 1);
    for (int i = 1; i <= n; i++)cin >> v[i];
    for (int i = 1; i <= n; i++)
    {
        a[i] = abs(v[i] - v[i - 1]);
    }
    
    buildtree(1, 1, n);
    ll l = 1, r = 1, len = 1, ans = 1;
    while (r <= n)
    {
        ll num = calc(1, 1, n, l, r);
        if (num > 1)r++;
        else l++;
        while (l > r)r++;
        ans = l;
        while (num>1&&ans <= n && ans > 1 && v[ans] % num == v[ans - 1] % num)
        {
            ans--;
        }
        if (r - ans > len)
        {
            len = r - ans;
        }
    }
    
    cout << len;
    return 0;
}