
本题是一个经典的线性动态规划问题。
首先,我们需要分析题目中的核心约束:"如果灯塔 ai−1 或ai+1 已经被点亮,则灯塔 ai 无法被点亮"。这意味着我们选出的灯塔集合中,任意两个灯塔的编号不能相邻。
题目提到可以挑选一个"子序列",这实际上是一个提示:只要我们选出的灯塔编号集合满足"互不相邻"的条件,我们总能找到一种操作顺序(例如按照编号从小到大,或者隔开操作),使得这些灯塔全部被成功点亮。因此,问题转化为:在给定的可用编号集合中,选出最多的元素,使得任意两个元素值不相邻(即不连续)。
我们建立动态规划状态:
定义 dpi表示:在只考虑编号为 1 到 i 的灯塔的情况下,最多能点亮的灯塔数量。
我们考虑如何转移:
对于第 i 号灯塔,我们有两种决策情况:
-
不点亮第 i 号灯塔(或者第 i号灯塔根本不在给定的输入序列 a 中): 此时,最大数量直接继承自前一个状态dpi−1。
dpi=dpi−1
-
尝试点亮第 i号灯塔(前提是输入序列 a 中包含 i): 如果我们要点亮 i,那么根据约束,我们绝对不能点亮 i−1。因此,我们的收益是"1 到i−2 的最大值"加上当前这 1 个。
dpi=max(dpi−1,dpi−2+1)
为了优化空间,观察到计算 dpidpi 只需要 dpi−1dpi−1 和 dpi−2dpi−2 两个状态,我们可以使用滚动变量
java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] nm = br.readLine().split(" ");
int n = Integer.parseInt(nm[0]);
int m = Integer.parseInt(nm[1]);
String[] arr = br.readLine().split(" ");
// 标记哪些编号在序列中出现过
Set<Integer> exist = new HashSet<>();
for(String s : arr){
exist.add(Integer.parseInt(s));
}
// 滚动变量:pre2=dp[i-2], pre1=dp[i-1], cur=dp[i]
int pre2 = 0; // dp[0]=0
int pre1 = exist.contains(1) ? 1 : 0; // dp[1]
int ans = pre1;
for(int i = 2; i <= n; i++){
int cur;
if(!exist.contains(i)){
cur = pre1; // 不能选i,继承dp[i-1]
}else{
cur = Math.max(pre1, pre2 + 1); // 选/不选取最大
}
pre2 = pre1;
pre1 = cur;
ans = cur;
}
System.out.println(ans);
}
}