洛谷 P1799 数列 题解
题目链接
Task 1
这是一道明显的删数问题,我们设 \(dp_{i,j}\) 表示前 \(i\) 个数中删 \(j\) 个后,前 \(i-j\) 个数(即原来前 \(i\) 个数)中在自己位置上的数的最大个数。则当当前数删时,\(dp_{i,j}=dp_{i-1,j-1}\);当当前数不被删时,还需考虑这个数是否在自己位置上,即 \([a_i=i-j]\)(\([P]\) 表示艾弗森括号,真为 \(1\),假为 \(0\)),所以此时 \(dp_{i,j}=dp_{i-1,j}+[a_i=i-j]\)。
综上,\(dp_{i,j}=\begin{cases}dp_{i-1,j}+[a_i=i-j] &j=0 \\\max{(dp_{i-1,j-1},dp_{i-1,j}+[a_i=i-j])} &j>0\end{cases}\)。代码如下,时间复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n;
int a[N],dp[N][N];
int main(){
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d",a+i);
for (int i=1;i<=n;++i){
for (int j=0;j<=i;++j) dp[i][j]=max(j>0?dp[i-1][j-1]:-1,dp[i-1][j]+(a[i]==i-j)); // 即状态转移方程
}
int ans=0;
for (int i=1;i<=n;++i) ans=max(ans,dp[n][i]);
printf("%d",ans);
return 0;
}
Task 2:滚动数组
观察到 \(dp_{i,j}\) 的更新只与 \(dp_{i-1,j-1}\)(如果存在)及 \(dp_{i-1,j}\) 有关,所以可以滚动数组优化,具体过程略过,详见这篇题解。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n;
int a[N],dp[N];
int main(){
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d",a+i);
for (int i=1;i<=n;++i){
for (int j=i;j>=0;--j) dp[j]=max(j>0?dp[j-1]:-1,dp[j]+(a[i]==i-j));
}
int ans=0;
for (int i=1;i<=n;++i) ans=max(ans,dp[i]);
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号