【题解】P1020 导弹拦截

题面

题目传送门

前言

本来开开心心敲个 \(O(n^2)\) 走人的……

我嘞个超绝 \(200\) 分啊!

正文

首先嗷,这个题是可以 \(O(n^2)\) 过滴!

转化一下题意:第一问即求最长不上升子序列的长度,第二问即求最少能被划分成多少个不上升子序列

第一问

DP 是非常好想的,记 \(dp1_i\) 为截止到 \(i\)\(a_i\) 必选的第一问的答案

转移方程:\(dp1_i = \max \lbrace {dp1_j + 1} \rbrace,a_j \ge a_i\)

答案的计算也是显然的……形如:\(ans = \mathop{\max}\limits_{i=1}^{n} \lbrace dp1_i \rbrace\)

但是捏,我们要追求一下 \(200\) 分,需要给出一种优于 \(O(n^2)\) 的算法

这个东西长得就很像决策单调性优化 DP 的套路

如何证明该 DP 转移有决策单调性?

\(f_i\) 表示对于所有长度\(i\) 的单调不升子序列,它的最后一项的大小的最大值。特别地,若不存在则 \(f_i = 0\)

显然有 \(f_i\)\([1,n]\) 上单调不升

不严格的证明
考虑使用反证法。假设 \(\exists u < v\),满足 \(f_u < f_v\)
考虑长度为 \(v\) 的单调不升子序列,根据定义它以 \(f_v\) 结尾
而我们可以从该序列中构造出一个长度为 \(u\) 的单调不升子序列,它的结尾同样是 \(f_v\),那么由于 \(f_v>f_u\),与 \(f_u\) 最大相矛盾,故假设不成立

嗯嗯,对于 \(f_i\) 的单调性我们有了不严格的证明捏!

现在考虑以 \(i\) 结尾的单调不升子序列的长度的最大值 \(dp1_i\)

由于我们需要计算所有满足 \(h_j > h_i\) 中,\(dp1_j\) 的最大值(我们称 \(j\)\(i\) 的决策点,显然有 \(j\) 是关于 \(i\) 的函数)

考察这个 \(dp1_j\) 的最大值,不妨设 \(x=dp1_j\),那么如果 \(a_i > f_x\),由于 \(f_x \le a_j\) 就有 \(a_i > a_j\),与 \(a_i \le a_j\) 矛盾,故假设不成立

因此总有 \(a_i \le f_x\)

以上我们证明了 DP 具有的决策单调性,采用二分法即可(当然 upper_boundlower_bound 好用捏!)

时间复杂度 \(O(n \log n)\)

第二问

前置知识:Dilworth 定理

Dilworth 定理
最长反链 \(=\) 最小链覆盖

然后第二问就进一步转化为求最长上升子序列的长度

同上即可!

代码

#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e6+10;
int n=1,a[maxn];
int dp1[maxn],dp2[maxn];
int len1,len2;
signed main(){
	while(cin>>a[n]){
		n++;
	}
	n--;
	len1=1;
	len2=1;
	dp1[1]=a[1];
	dp2[1]=a[1];
	for(int i=2;i<=n;i++){
		if(a[i]<=dp1[len1]){
			dp1[++len1]=a[i];
		}else{
			int p=upper_bound(dp1+1,dp1+len1+1,a[i],greater<int>())-dp1;
			dp1[p]=a[i];
		}
		if(a[i]>dp2[len2]){
			dp2[++len2]=a[i];
		}else{
			int p=lower_bound(dp2+1,dp2+len2+1,a[i])-dp2;
			dp2[p]=a[i];
		}
	}
	cout<<len1<<endl<<len2<<endl;
	return 0;
}

后记

也许,云落真的很……

完结撒花!

posted @ 2024-12-24 18:38  sunxuhetai  阅读(16)  评论(0)    收藏  举报