题目大意
一个人流落在了群岛上,但是他要捡钻石。
一共有30001个岛子,编号从0到30000。这个人从0出发开始跳,第一次跳长度为
d
d
,即从0号岛跳到号岛。而后的跳跃遵循如下规则。
- 若上一次跳的长度为 last l a s t ,则这一次可以跳 last−1,last,last+1 l a s t − 1 , l a s t , l a s t + 1 三种步长。
跳的长度不能为0。
每跳到一个岛子上,他就会捡这个岛子上的钻石。
求他最多捡多少钻石。
思路
一看就应该是DP。考虑最裸的方程。
f[i][j] f [ i ] [ j ] 表示跳到第 i i 个岛子,上一步步长为,最多捡多少钻石。
转移显然
f[i][j]→f[i+j][j],f[i+j−1][j−1],f[i+1+1][j+1]
f
[
i
]
[
j
]
→
f
[
i
+
j
]
[
j
]
,
f
[
i
+
j
−
1
]
[
j
−
1
]
,
f
[
i
+
1
+
1
]
[
j
+
1
]
。
面临的问题也很显然:时间空间都是
300002
30000
2
,GG。
但是可以发现一个性质:
这个人步子长度的变化不会特别大
证明:等差数列求和,若他每次加一,即使是初始步长为1,不到250次也会超过30000
那就可以简化一下状态咯。
f[i][j] f [ i ] [ j ] 表示跳到第 i i 个岛子,上一步的步长与初始步长的差距为 j j <script type="math/tex" id="MathJax-Element-28">j</script>,最多捡到多少钻石。
转移和原来是一样的,控一下边界,因为会有复数第二维开两倍,结束。
代码
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#include<cctype>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
const int MAXN=30010;
const int N=300;
int f[MAXN*2][2*N+10];
int cnt[MAXN*2];
int main()
{
int n,d;
scanf("%d%d",&n,&d);
for (int i=1;i<=n;i++)
{
int x=read();
cnt[x]++;
}
memset(f,-1,sizeof(f));
f[d][N]=cnt[d];
for (int i=d;i<30000;i++)
{
for (int j=0;j<=2*N;j++)
{
if (f[i][j]==-1) continue;
for (int x=-1;x<=1;x++)
{
int len=j-N+d+x;
if (len<1 || i+len>MAXN || !(j+x)) continue;
f[i+len][j+x]=max(f[i+len][j+x],f[i][j]+cnt[i+len]);
}
}
}
int ans=0;
for (int i=d;i<=30000;i++)
for (int j=0;j<2*N;j++)
ans=max(ans,f[i][j]);
cout<<ans;
return 0;
}