本文已参与[新人创作礼]活动,一起开启掘金创作之路
题目描述
这是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;
}