CF1453F Even Harder 题解

Codeforces
Luogu

Description.

有一张 \(n\) 个点的图,\(i\) 可以走到 \([i+1,i+a_i]\)
现在删掉若干点,编号不变,使得 \(1\)\(n\) 只有一条路径。
最少化点数。

\(n\le 3000\)

Naive Solution.

首先考虑分析答案的性质,设序列为 \(\{p_i\}\)

  1. \(p_i+a_{p_i}\ge p_{i+1}\) 因为至少要有一条路
  2. \(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;}
posted @ 2021-11-01 16:32  Peal_Frog  阅读(43)  评论(0编辑  收藏  举报