目录
目录
题目
题目描述
在草场上有一条直线,直线上有若干个目标点。每个目标点都有一个分值和一个坐标。现在你可以选择其中任意一个目标点开始跳,只能沿一个方向跳,并且必须跳到另一个目标点。且每次跳的距离都不能少于上一次的距离。请问你能得到的最大分值是多少?
输入
第一行一个整数N(1<=N<=1000).接下来从第二行到第N+1行,每一行包含两个整数x(i)和p(i),每个整数在区间[0,1000000]之间。
输出
输出格式:最大能获得的分值。
样例输入
6
5 6
1 1
10 5
7 6
4 8
8 10
样例输出
25
解析
易证:此题为二维dp
由此,我们可以用一个二维数组 f[i][j] 表示从 j 跳到 i 的最大得分,我们可以由此得出状态转移方程
如果就这样做的,时间复杂度是 的,所以我们可以进一步思考优化:改变循环的顺序!!!
为了方便,我们先考虑顺序的 dp 法,可以肯定,一定有 k < j < i,此时我们将 j 提出来,那么就有
一个适当的优化(我们首先进行尝试)
代码 1.0
#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 1000 + 5
#define reg register
using namespace std;
int n;
struct node{
int x,p;
bool operator < (const node &rhs) const{
return x < rhs.x;
}
}a[M];
int f[M][M],ans,h;
inline int max(int x,int y){
return x > y ? x : y;
}
int main(){
scanf("%d",&n);
for (reg int i = 1;i <= n; ++ i)
scanf("%d%d",&a[i].x,&a[i].p);
sort(a + 1,a + 1 +n);
for (reg int j = 1;j <= n; ++ j){
f[j][j] = a[j].p;
ans = max(ans,f[j][j]);
for (reg int i = j + 1;i <= n; ++ i){
for (reg int k = j;k >= 1; -- k){
if (a[j].x - a[k].x <= a[i].x - a[j].x ){
f[i][j] = max(f[i][j],f[j][k] + a[i].p);
ans = max(ans,f[i][j]);
}
}
}
}
for (reg int j = n;j >= 1; -- j){
f[j][j] = a[j].p;
for (reg int i = j - 1;i >= 1; -- i){
for (reg int k = j;k <= n; ++ k){
if (a[k].x - a[j].x <= a[j].x - a[i].x){
f[i][j] = max(f[i][j],f[j][k] + a[i].p);
ans = max(ans,f[i][j]);
}
}
}
}
printf("%d\n",ans);
return 0;
}
- 因为没有确定方向,所以要进行两次
- k 从 j 开始表明以 j 为起点
接着给一份WA证明:
再优化
既然不能AC,那就一定还存在优化方法,我们接着上文的方法继续研究,如果身为读者的你接下来会研究什么?我想,只剩下 i 与 k 了
我们进行一次假设吧,如果 i 越来越大,那么 k 呢?你会神奇地发现 k 的取值范围也变大了,强调一下,不是变小了,所以我们可以想到,上一次 k 的状态可以取到当前,所以我们用一个 sum 之类的变量存储一下, k 到 i 的最大得分就行了
代码 2.0
#include<cstdio>
#include<algorithm>
#define M 1000 + 5
#define reg register
using namespace std;
int n,ans;
struct node{
int id,poi;
bool operator < (const node &rhs) const {
return id < rhs.id;
}
}a[M];
int f[M][M];
inline int max(int x,int y){
return x < y ? y : x;
}
int main(){
scanf("%d",&n);
for (reg int i = 1;i <= n; ++ i)
scanf("%d%d",&a[i].id,&a[i].poi);
sort(a + 1,a + 1 + n);//重载,所以不需要 cmp
for (reg int j = 1;j <= n; ++ j){ //第一遍顺着来(k < j < i)
f[j][j] = a[j].poi;//初值
int sum = a[j].poi;//存 k 到 j 的最大得分,这儿是 j 到 j 的得分
ans = max(sum,ans);
int k = j - 1;
for (reg int i = j + 1;i <= n; ++ i){
sum += a[i].poi;
while ( k && a[j].id - a[k].id <= a[i].id - a[j].id){//因为 i 与 j 的距离只会越来越远,所以 k 的取值范围会越来越广
sum = max(sum,f[j][k] + a[i].poi);
-- k;
}
f[i][j] = max(f[i][j],sum);
ans = max(ans,f[i][j]);
sum -= a[i].poi;
}
}
for (reg int j = n;j >= 1; -- j){
f[j][j] = a[j].poi;
int sum = a[j].poi;
int k = j + 1;
for (reg int i = j - 1;i >= 1; -- i){
sum += a[i].poi;
while( k <= n && a[k].id - a[j].id <= a[j].id - a[i].id){
sum = max(sum,f[j][k] + a[i].poi);
++ k;
}
f[i][j] = max(f[i][j],sum);
ans = max(ans,f[i][j]);
sum -= a[i].poi;
}
}
printf("%d\n",ans);
return 0;
}
然而 sum 一会又加 a[i].poi,一会又减,很麻烦(因为这儿的 sum 存的是 j 到 k 的最大得分),则我们可以改变一下思路,让 sum 存储一下 k 到 j 的最大得分不就行了吗
代码 3.0
#include<cstdio>
#include<algorithm>
#define M 1000 + 5
#define reg register
using namespace std;
int n,ans;
struct node{
int id,poi;
bool operator < (const node &rhs) const {
return id < rhs.id;
}
}a[M];
int f[M][M];
inline int max(int x,int y){
return x < y ? y : x;
}
int main(){
scanf("%d",&n);
for (reg int i = 1;i <= n; ++ i)
scanf("%d%d",&a[i].id,&a[i].poi);
sort(a + 1,a + 1 + n);//重载,所以不需要 cmp
for (reg int j = 1;j <= n; ++ j){ //第一遍顺着来(k < j < i)
f[j][j] = a[j].poi;//初值
int sum = a[j].poi;//存最大得分,这儿是 j 到 j 的得分
ans = max(sum,ans);
int k = j - 1;
for (reg int i = j + 1;i <= n; ++ i){
while ( k && a[j].id - a[k].id <= a[i].id - a[j].id){//因为 i 与 j 的距离只会越来越远,所以 k 的取值范围会越来越广
sum = max(sum,f[j][k]);
-- k;
}
f[i][j] = max(f[i][j],sum + a[i].poi);
ans = max(ans,f[i][j]);
}
}
for (reg int j = n;j >= 1; -- j){
f[j][j] = a[j].poi;
int sum = a[j].poi;
int k = j + 1;
for (reg int i = j - 1;i >= 1; -- i){
while( k <= n && a[k].id - a[j].id <= a[j].id - a[i].id){
sum = max(sum,f[j][k]);
++ k;
}
f[i][j] = max(f[i][j],sum + a[i].poi);
ans = max(ans,f[i][j]);
}
}
printf("%d\n",ans);
return 0;
}