题目描述
传送门:P1020 导弹拦截
分析:第一问,很明显求最长不递增子序列,dp模板;第二问,则求不递增子序列的最少个数,根据dilworth定理,不递增子序列的最少个数=最长递增子序列的长度。即两问都是LIS问题。
Dilworth定理
若读者有一定离散基础并有兴趣的话,可直接看百度百科狄尔沃斯定理里的归纳法证明。若无离散和相关数学基础但也有兴趣的话,则推荐B站视频:[Dilworth定理] 洛谷 P1020 导弹拦截
以下给出笔者的一些粗陋想法:
每条不递增子序列都满足两个性质:下标递增且数值不递增。
假设已知有k条最长的不递增序列且除起点和终点外没有交点,那么当加入第j个数时(j=k+1),枚举各序列末尾除重合终点外的数x1,x2…xk,若存在xi(1<=i<=k)不大于xj,那么xj就能加入该序列末尾(符合下标递增且数值不递增的性质),若不存在则说明xj必大于xi(i从1到k的所有值),而xj大于xi使最长递增序列的个数+1,此时只能把xj当作一条新的序列(或者说链)的起点,即每条(除重合起点外)链头的下标和数值都能在上一条中找到比它小的,符合递增子序列性质。
结论:
不递增子序列的最少个数=最长递增子序列的长度
不递减子序列的最少个数=最长递减子序列的长度
朴素dp(O(n^2))
定义dp[i]存储以a[i]结尾的最长不递增子序列的长度。可以想到每放一个a[i]就用j遍历0~i-1,若a[j]>=a[i],说明a[i]可放进a[j]后面,此时dp[i] = dp[j] + 1,因题目取最优解,每次遍历取最大值即可。
状态转移方程:dp[i] = max(dp[i],dp[j]+1)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int a[N];
int dp[N];//定义dp[i]=以a[i]结尾的最长不递增序列长度
int cnt;
int main(){
while (cin>>a[cnt++]);
for (int i=0;i<cnt-1;i++) dp[i] = 1;
for (int i=0;i<cnt-1;i++){
for (int j=0;j<i;j++){
if (a[i]<=a[j]) dp[i] = max(dp[i],dp[j]+1);
//若符合不递增性质,状态转移
}
}
int ans = -1;
for (int i=0;i<cnt-1;i++){
//printf("dp[%d] = %d\n",i,dp[i]);
ans = max(dp[i],ans);
//最后答案取最大值
}
cout<<ans<<endl;
//求最长递增同理
for (int i=0;i<cnt-1;i++) dp[i] = 1;
for (