题目描述
难度分:1800
输入T(≤5×104)表示T组数据。所有数据的n2之和≤4×106。
每组数据输入n(1≤n≤2000),l(1≤l≤109)和长为n的数组,其中第i个元素是数对(ai,bi),范围[1,109]。
你可以打乱这n个元素的顺序。选出数组中的一个子序列(不一定连续),要求满足
sum(子序列中的a)+sum(子序列中相邻两个b的绝对差)≤l
输出子序列长度的最大值。
输入样例
5
5 8
4 3
1 5
2 4
4 3
2 3
1 6
4 10
3 12
4 8
2 1
2 12
5 26
24 7
8 28
30 22
3 8
17 17
5 14
15 3
1000000000 998244353
179 239
228 1337
993 1007
输出样例
3
1
2
1
0
算法
动态规划
没昨天那个1400的题思维难度高,n≤2000感觉就是动态规划,往这上面靠就能做出来。先对(ai,bi)按照bi排序,然后进行DP
,这样就不用考虑绝对值了,肯定是后一个b大于等于前一个b。
状态定义
f[i][j]表示选择i,且一共选择j个二元组的情况下,能够达到的最小Σjk=1aik+Σj−1k=1|bik−bik−1|,其中ik表示选出来j个二元组中的第k个。在这个定义下,DP
完成后,遍历i∈[1,n],对于固定的i遍历j∈[1,i],最大满足f[i][j]的j就是答案。
状态转移
双重循环遍历i∈[1,n],j∈[1,n],然后考虑倒数第二个位置last<i,有状态转移方程
f[i][j]=minlast<if[last][j−1]+ai+bi−blast
前缀最小值优化
此时如果遍历last,那整个算法的时间复杂度就是O(n3),不可接受。但是我们发现,f[last][j−1]−blast可以放在一起,因此没有必要去遍历last,只要维护一个前缀最小值就可以让状态转移从O(n)变成O(1)。
复杂度分析
时间复杂度
状态数量是O(n2),前缀最值优化后,单次转移的时间复杂度为O(1)。因此,整个算法的时间复杂度为O(n2)。
空间复杂度
空间瓶颈就是DP
矩阵f,所以额外空间复杂度为O(n2)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2010;
LL f[N][N];
int t, n, l;
struct Node {
int a, b;
bool operator<(const Node other) const {
return b < other.b;
}
} nodes[N];
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &l);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &nodes[i].a, &nodes[i].b);
}
sort(nodes + 1, nodes + n + 1);
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
f[i][j] = 1e13;
}
}
for(int i = 1; i <= n; i++) {
f[i][0] = 0;
}
for(int j = 1; j <= n; j++) {
LL minpre = f[0][j - 1];
for(int i = 1; i <= n; i++) {
if(j == 1) {
f[i][j] = nodes[i].a;
}else {
f[i][j] = minpre + nodes[i].a + nodes[i].b;
}
minpre = min(minpre, f[i][j - 1] - nodes[i].b);
}
}
int ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= i; j++) {
if(f[i][j] <= l) {
ans = max(ans, j);
}
}
}
printf("%d\n", ans);
}
return 0;
}