【分块构造】1654E - Arithmetic Operations
好久没有单独为一道题出题解,但是这道题得确有意义单出一篇题解来做为笔记记录
E. Arithmetic Operations
You are given an array of integers 𝑎1,𝑎2,…,𝑎𝑛a1,a2,…,an.
You can do the following operation any number of times (possibly zero):
- Choose any index 𝑖i and set 𝑎𝑖ai to any integer (positive, negative or 00).
What is the minimum number of operations needed to turn 𝑎a into an arithmetic progression? The array 𝑎a is an arithmetic progression if 𝑎𝑖+1−𝑎𝑖=𝑎𝑖−𝑎𝑖−1ai+1−ai=ai−ai−1 for any 2≤𝑖≤𝑛−12≤i≤n−1.
The first line contains a single integer 𝑛n (1≤𝑛≤1051≤n≤105).
The second line contains 𝑛n integers 𝑎1,𝑎2,…,𝑎𝑛a1,a2,…,an (1≤𝑎𝑖≤1051≤ai≤105).
Print a single integer: the minimum number of operations needed to turn 𝑎a into an arithmetic progression.
9
3 2 7 8 6 9 5 4 1
6
14
19 2 15 8 9 14 17 13 4 14 4 11 15 7
10
10
100000 1 60000 2 20000 4 8 16 32 64
7
4
10000 20000 10000 1
2
大致题意:
给定一个数列,用最少的修改次数使其变为一个等差数列。
题解
该题一个想法是改考虑修改为考虑最少保留多少个数它们为等差数列。具体为(i,ai)映射到笛卡尔坐标系上就是考虑一条直线包含最多的点数。
当等差数列的长度较长时,它的等差数列差值就不会很大,而差值大则等差数列的长度就不会长。
故我们考虑分块,当等差差值d<S时,我们枚举每一个d,O(n)扫一遍,将a[i]改为a[i]-i*d,然后找一下里面有多少最多的相等的数。时间复杂度O(n根号n)
而当等差差值d>S的时候,我们有两种做法。
第一种:我们原本有一种O(n^2)的DP做法,即f[i][d]表示以i结尾的以d为公差的最长等差数列多长。转移枚举1->i-1就可以了。若只考虑d>S时,则i其最多只能从i-[M/S]的地方转移过来,否则就超出数值范围了,故我们由O(N^2)成功转变为了O(n根号n)
第二种:这位大佬的做法我们可以将整个序列分成S长度的块,在每个块之间S^2跑暴力,然后再在S块的中间位置隔开,即由划分成S长度的块,每个块内S^2跑暴力。eg:设块长1000,则先[1000k+1,1000k+1000]内暴力,然后[1000k-500,1000k+500]内跑暴力,这样就暴力跑完了所有块长S/2,即例中500长度的暴力,也就完成了任务。
具体实现上来讲,我们可以利用reverse数组,实现我们考虑的所有d都是正的,方便我们存储数字。
下面是我的一个第一种方法的实现,具体该题还可以看官方题解。
实现
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int FK = 300;
int n;
int a[maxn];
int b[maxn + FK * maxn];
int ans = 1;
unordered_map<int, int> dp[maxn];
void solve() {
for (int s = 0; s <= FK; s++) {
for (int i = 1; i <= n; i++) {
ans = max(ans, ++b[a[i] + s * (i - 1)]);
}
for (int i = 1; i <= n; i++) {
--b[a[i] + s * (i - 1)];
}
}
for (int i = 1; i <= n; i++) {
dp[i].clear();
for (int j = max(1, i - (maxn / FK) - 1); j < i; j++) {
int d = (a[i] - a[j]) / (i - j), r = (a[i] - a[j]) % (i - j);
if (d > FK && r == 0) {
if (dp[j].count(d))
dp[i][d] = max(dp[i][d], dp[j][d] + 1);
else
dp[i][d] = max(dp[i][d], 1);
ans = max(dp[i][d] + 1, ans);
}
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
solve();
reverse(a + 1, a + 1 + n);
solve();
printf("%d", n - ans);
return 0;
}