【题解】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_bound
和 lower_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;
}
后记
也许,云落真的很……
完结撒花!