给定 n 个整数 a1, a2, …, an 和⼀个 d,你需要选出若⼲个整数,使得将这些整数从⼩到⼤排好序之 后,任意两个相邻的数之差都不⼩于给定的 d,问最多能选多少个数出来。
【输⼊格式】 第⼀⾏两个整数 n,d (1<=n<=105, 0<=d<=109),分别表⽰整数个数和相邻整数差的下界。 第⼆⾏ n 个整数 a1, a2, …, an (1<=ai<=10^9, 1<=i<=n),表⽰给定的 n 个整数。
【输出格式】 仅⼀⾏⼀个整数,表⽰答案。
【样例输⼊】
6 2
1 4 2 8 5 7
【样例输出】
3
【解释】 注意,选出的数在排序后,相邻两数之差不⼩于给定值。 ⽐如,对于给定值 2,[1 4 7] 是⼀个满⾜条件的选择⽅案,但是[1 4 5] 却不是,因为 5 - 4 = 1 < 2。 在本样例中,[1 4 7],[1 4 8],[1 5 7],[1 5 8],[2 4 7],[2 4 8] 都是满⾜要求的选择⽅案,但是⽆论 如何都没有办法得到⼀个选出 4 个数且满⾜条件的⽅案,所以本样例的答案为 3。
【时空限制】 2500ms,256MB
这道题目,我只过了部分测试点,原因是超时了。我没有做到具体问题具体分析,滥用了动态规划,时间复杂度没有符合要求。下面我先给出自己考场上的代码,给大家做个反面教材,之后给出正确的代码。
考场代码(不能AC):
#include
#include
#include
using namespace std;
int dp[100010];
int max(int a, int b) { return a > b ? a : b; }
int main() {
int n, d, ret =0;
scanf("%d%d", &n, &d);
vector<int>V(n);
for (int i = 0; i < n; i++)
scanf("%d", &V[i]);
sort(V.begin(), V.end());
for (int i = 0; i < 100010; i++)
dp[i] = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (V[j] - V[i] >= d)
dp[j] = max(dp[j], dp[i] + 1);
if (dp[j] > ret)
ret = dp[j];
}
}
printf("%d", ret + 1);
return 0;
}
分析:上面代码中,我使用了STL中的排序算法,时间复杂度为O(nlogn),但是我用动态规划找最长递增序列的时候,时间复杂度是O(n2);数据量是105,运算量级1010显然超过了时间限制。
在这里真的要用动态规划吗?我先说结论,其实用不到,对排序之后的序列,从小到大取数字,每次取满足条件的最小的那个数字,最后取出的总数最多。
为什么这么说呢?示例部分给出的序列是[1 4 2 8 5 7 ],取出的数字有多种情况,其实正是这一点欺骗到了我,让我觉得每次取最小最后的结果不一定是取出最多的。我现在给出一个序列[2 3 1 7 6 8],d=2,排序后为[1 2 3 6 7 8],我们发现了什么,只有[1 3 6 8]是可以有4个数满足条件的,但凡你不是每次取最小的,你都取不出四个。
这个也比较容易理解,假设我在选数字的时候ai < ai+1,并且这两个数都满足挑选条件,我取出的最长数字序列为[b1 b2 … ai+1 … bn],那么序列[b1 b2 … ai … bn]也一定是满足条件的。所以每次选最小的,最后总的取出的数字数量一定是最多的。
Update:
#include
#include
#include
using namespace std;
int main() {
int n, d, ret = 0;
scanf("%d%d", &n, &d);
vector<int>V(n);
for (int i = 0; i < n; ++i)
scanf("%d", &V[i]);
sort(V.begin(), V.end());
for (int i = 0; i < n;) {
int j = i + 1;
for (; j < n; ++j) {
if (V[j] - V[i] >= d) {
++ret;
i = j;
break;
}
}
if(j==n)
break;
}
printf("%d", ret + 1);
return 0;
}