2022-04-20每日刷题打卡
代码源——每日一题
最长同余子数组 - 题目 - 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%ma2%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也能过。
#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;
}
CodeForces——线段树专题
B - Segment Tree, part 2 - B. Inverse and K-th one
There is an array of n booleans, initially filled with zeros. You need to write a data structure that processes two types of queries:
change the value of all elements on a segment from l to r−1 to the opposite,
find the index of the k-th one.
Input
The first line contains two numbers n and m (1≤n,m≤100000), the size of the array and the number of operations. The following lines contain the description of the operations. The description of each operation is as follows:
1 l r: change the value of all elements on a segment from l to r−1 to the opposite (0≤l<r≤n),
2 k: the index of the k-th one (ones are numbered from 0, it is guaranteed that there are enough ones in the array).
Output
For each operation of the second type, print the index of the corresponding one (all indices in this problem are from 0).
Example
input
5 7
1 1 3
2 1
1 0 2
2 0
2 1
1 0 5
2 2
outpu
2
0
2
4
这题是说,一开始给你一个数组全是0,有两种操作,一个是修改一个区间里的数组状态,1变0,0变1。一个是让你找到第k个1的位置。
建线段树,每个节点存的是当前区间的区间和,我们把lz标记变成bool类型,当lz为true时,我们就把当前节点的值修改并把标记下传。怎么修改?我们修改数值是把0变成1,1变成0,我们节点计算的是区间和,那么变化后的区间和+变化前的区间和应该=区间长度。
然后至于询问,我们每次根据两个子节点的值判断该去左边还是右边:当左边的区间和大于等于k时,说明第k个1应该在左节点;反之我们去右边找,同时把k减去左节点的值。
(这题有点问题,他说第k个1,但是k的范围是0n-1的,为了方便我们还是改成1n比较好)
#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];
bool b[4 * N];
void revise(ll k, ll l, ll r, ll x, ll y)
{
if (l == x && r == y)
{
f[k] = r - l - f[k] + 1;
b[k] = !b[k];
return;
}
int m = (l + r) / 2;
if (b[k])
{
b[k + k] = !b[k + k];
f[k + k] = (m - l + 1) - f[k + k];
b[k + k + 1] = !b[k + k + 1];
f[k + k + 1] = (r - m) - f[k + k + 1];
b[k] = false;
}
if (y <= m)revise(k + k, l, m, x, y);
else
if (x > m)revise(k + k + 1, m + 1, r, x, y);
else
{
revise(k + k, l, m, x, m);
revise(k + k + 1, m + 1, r, m + 1, y);
}
f[k] = f[k + k] + f[k + k + 1];
}
ll calc(ll k, ll l, ll r, ll x)
{
if (l == r)return l;
int m = (l + r) / 2;
if (b[k])
{
b[k + k] = !b[k + k];
f[k + k] = (m - l + 1) - f[k + k];
b[k + k + 1] = !b[k + k + 1];
f[k + k + 1] = (r - m) - f[k + k + 1];
b[k] = false;
}
ll res = -1;
if (f[k + k] >= x)res = calc(k + k, l, m, x);
else res = calc(k + k + 1, m + 1, r, x - f[k + k]);
f[k] = f[k + k] + f[k + k + 1];
return res;
}
int main()
{
cin.sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(b, false, sizeof b);
int n, t;
cin >> n >> t;
while (t--)
{
int st, x, y;
cin >> st >> x;
if (st == 1)
{
cin >> y;
revise(1, 1, n, x + 1, y);
}
else
{
cout << calc(1, 1, n, x + 1) - 1 << endl;
}
}
return 0;
}