题目描述
有 N 个整数,从左往右排成一行,现在你要删除其中的若干个数,不妨假设剩下的数,从左往右,重新标号为:
a1,a2,a3,a4,…,ak 。
那么必须使得: a2−a1=a3−a2=a4−a3=…=ai−ai−1 。
例如: N=5 ,这 5 个数是: 1 8 4 7 10。
那么删除 8 之后,剩下的数是:1 4 7 10,可以发现: 4-1 = 7-4 = 10-7。
你的任务是计算:在满足上述条件下,最多可以剩下多少个数?
输入格式 1827.in
第一行,一个整数 N 。
1≤N≤2000 。
第二行, N 个整数。每个整数范围:[0,109] 。 有接近50%的数据,每个整数的范围是: [0,1000] 。
输出格式 1827.out
一个整数。
输入样例 1827.in
5
1 4 3 5 7
输出样例 1827.out
4
样例解释
删除4后,剩下的1 3 5 7满足题意。
既然 a2−a1=a3−a2=a4−a3=…=ai−ai−1 ,那么也就意味着删除后的序列必然是一个等差数列,而且要注意递增和递减都是有可能的。
可以看到
n≤2000
,比较小,
n2
的复杂度是可以支持得住的,不妨从这里入手。
在最终的等差数列中,如果我们通过枚举确定了前两项
ai
和
aj
,也就得到了该数列的公差
d
。于是就可以往后不断查找
上面这个做法中,前面的枚举并没有问题,因为至少要确定两个量才能确定一个等差数列。但后面的查找部分,如果再用一个
O(n)
往
j
的后面扫一遍,时间复杂度就达到了
其实,我们在确定了
ai
和
aj
之后的关键就是,快速判断是否有
ak
,满足
k>j
且
ak=aj+(aj−ai)
。如果有再迭代,直至找到最后一个满足条件的
k
<script type="math/tex" id="MathJax-Element-1179">k</script>。
这其实是很满足我们对于 hash 的要求的。
不妨在 hash 表中记两个值,原始数字和位置。读入时就将每个数标记好。之后要判断时直接在 hash 表中找就可以了。但是要注意,这题用拉链法会更好。
请设想,数字的范围较大,冲突的可能性实际上是比较小的。如果用线性探测法,可能还要找比较多次。但是拉链法则可以将同 hash 值的聚在一起,更方便。
此外还要注意细节:要记得开 long long,答案最小是取1,处理越界等问题。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
typedef long long LL;
const int maxn = 2000 + 10;
const int prime = 1000007;
struct Data { LL value; int info; }; //数值和位置
int n;
LL a[maxn];
#define inf 1000000000 //最大值,越界就不必考虑了
vector <Data> hash[prime];
void insert1(LL val, int pos) { //往 hash 表中插入值
int y = (val + inf) % prime;
hash[y].push_back((Data){val, pos});
}
int find1(LL val) {
int y = (val + inf) % prime; //注意差值可能为负数,统一加上偏移量!
int ret = n;
int len = hash[y].size();
for (int i = 0; i < len; i++)
if (hash[y][i].value >= val) ret = min(ret, hash[y][i].info); //贪心思想:如果有多个数,找最前面的一个
if (ret == n) return -1; else return ret; //编号为 0~n-1,若扫完一遍仍为 n 则说明找不到
}
int main(void) {
freopen("1827.in", "r", stdin);
freopen("1827.out", "w", stdout);
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
insert1(a[i], i);
}
int ans = 1;
for (int i = 0; i + 1 < n; i++) {
for (int j = i + 1; j < n; j++) {
LL c = a[j] - a[i]; //数列公差
int sum = 2; //数列长度,至少有 a[i] 和 a[j] 这两项
LL num = a[j] + c;
int p = j; //迭代用
int t = find1(num);
while (t > p) {
sum++;
p = t;
num += c;
if (num>inf||num<0)break; //越界处理
t = find1(num);
}
ans = max(ans, sum);
}
}
printf("%d\n", ans);
return 0;
}