Link.
Description.
有一张 \(n\) 个点的图,\(i\) 可以走到 \([i+1,i+a_i]\)。
现在删掉若干点,编号不变,使得 \(1\) 到 \(n\) 只有一条路径。
最少化点数。
\(n\le 3000\)
Naive Solution.
首先考虑分析答案的性质,设序列为 \(\{p_i\}\)
- \(p_i+a_{p_i}\ge p_{i+1}\) 因为至少要有一条路
- \(p_i+a_{p_i}< p_{i+2}\) 根据 \(1\),\(p_{i+1}\) 能跳到 \(p_{i+2}\),所以出现了两条路,不行
然后考虑 \(dp_{i,j}\) 表示当前在 \(i\),上一个是 \(j\)。
那接下来只能选择 \((j+a_j,i+a_i]\) 的点,转移到 \(dp_{i,k}\)。
是 \(O(n^3)\) 的。
另一种思路,因为值域很小,可以把 \(dp_{i,v}\) 表示当前在 \(i\),上一个能到 \(j\) 的方案数。
那相当于就是 \(dp_{j,k}+1\rightarrow dp_{i,j+a_j},j<i,k<i\),前缀最大值一下就行了。
发现假了,因为不一定 \(p_i+a_{p_i}< p_{i+2}\),详见样例 3
Solution.
直接考虑 \(dp_{i,w}\),表示当前在 \(i\) 上一个能跳到的位置是 \(w\),有 \(i\le w\)。
那就有 \(dp_{i,j}+cnt\rightarrow dp_{w,i},w>j+a_j\)。
其中 \(cnt\) 表示要从 \(j\) 转移到 \(i\) 需要删掉的数的个数。
\(\therefore cnt=\sum_{k=j+1}^{i-1}[a_k+k\ge i]\)
然后相当于可以在 \(j+a_j\) 打标记取前缀最大值转移。
Coding.
点击查看代码
//Coded by Kamiyama_Shiki on 2021.10.31 {{{
//是啊,你就是那只鬼了,所以被你碰到以后,就轮到我变成鬼了
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
template<typename T>inline void read(T &x)
{
x=0;char c=getchar(),f=0;
for(;c<48||c>57;c=getchar()) if(!(c^45)) f=1;
for(;c>=48&&c<=57;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
f?x=-x:x;
}
template<typename T,typename...L>inline void read(T &x,L&...l) {read(x),read(l...);}//}}}
const int N=3005;int n,a[N],dp[N][N];
inline void solve()
{
read(n);for(int i=1;i<=n;i++) read(a[i]);
memset(dp,0x3f,sizeof(dp));for(int i=2;i<=n;i++) dp[i][1]=0;
for(int i=2,cnt=0;i<=n;i++,cnt=0)
{
for(int j=i-1;j>=1;j--) if(j+a[j]>=i)
dp[j+a[j]+1][i]=min(dp[i][j]+cnt++,dp[j+a[j]+1][i]);
for(int j=i+1;j<=n;j++) dp[j][i]=min(dp[j-1][i],dp[j][i]);
}
//for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) printf("%d%c",dp[i][j],j==n?'\n':' ');
printf("%d\n",dp[n+1][n]);
}
int main() {int Ca;for(read(Ca);Ca--;) solve();return 0;}