P1020 导弹拦截 || 偏序集与 Dilworth 定理

时隔 \(2\) 年,终于把导弹拦截的 \(O(n \log n)\) 写了。
偏序关系
现在有一个二元关系 \(R\),不妨设 \(R\) 是定义在 \(S\) 上的,如果 \(R\) 满足一下性质,称其为偏序关系。
(下面的小于等于号并非真的是小于等于号,而是偏序关系的符号,但 \(\le\) 也是一个偏序关系)
- 自反性: \(a \le a, a \in S\)
- 传递性:\(\forall a,b,c \in S \ \ ,a \le b,b\le c \to a \le c\)
- 反对称性:$\forall a,b \in S, a \le b,b \le a \to a=b $
偏序集
偏序集就是由定义在 \(S\) 上的偏序 \(R\) 和集合 \(S\) 组成的,记作 \((S,R)\),最经典的例子就是 \((Z, \le )\),此处的小于等于是真的小于等于号。
对于 \(S\) 中的元素 \(a,b\) 如果 \(a \le b\) 或 \(b \le a\),则称 \(a,b\) 可比,此处小于等于号是偏序关系的符号。
哈斯图
对于元素 \(x\),若有 \(x < y\) 且不存在 \(z\),使得 \(x < z < y\),就连出来一条 \(x \to y\) 的有向边,这样生成的图就是哈斯图。
比如集合 \(\{1,2,3,4,6,8,12 \}\) 上定义关系 \(\{ (a,b) | a \ 整除 \ b \}\) 画出哈斯图就长这样:

显然哈斯图一定是 DAG。
偏序集上的链与反链
链:链上任意两两元素都可比。
反链:链上任意两两元素都不可比。
Dilworth 定理
对于任意有限偏序集,都有最小链划分(不可交)所划分的链的个数等于最大反链所包含元素,对上图显然成立。

蓝色是最小链划分,红色是最大反链。
证明
利用数学归纳法,先假设有一偏序集 \((S,\le )\),\(|S|=n\)。
当 \(n=1\) 时,结论显然成立。
假设 \(n=k\) 的结论已被证明,下面证明 \(n=k+1\) 时也成立。
设 \(A\) 是最大反链,计为 \(A=\{a_1,a_2,\dots ,a_w \}\)。
定义:
\(D(A)= \{x | \exists a \in A,x < a \}\)
\(U(A)= \{x | \exists a \in A,x > a \}\)
有 $S=A \cup D(A) \cup U(A) $。
-
如果存在最大反链 \(A\),使得 \(D(A),U(A)\) 均非空。因为 \(A\) 是 \(S\) 的最大反链,所以 \(A\) 也是 \(A \cup D(A)\) 的最大反链由归纳假设 \(A \cup D(A)\) 可以划分为 \(c_1,c_2,\dots ,c_w\) 共 \(w\) 条链,\(c_i\) 的最大元为 \(a_i\),同理 \(A \cup U(A)\) 可以划分 \(d_1,d_2,\dots ,d_w\) 共 \(w\) 条链,\(d_i\) 的最小元为 \(a_i\),于是 \(S\) 就可以划分为 $c_i \cup d_i $ 共 \(w\) 条链。
-
反之,那么每条反链 \(A\) 都有全部的最大元或全部的最小元。在 \(S\) 中选择极大元 \(y\),再选择极小元 \(x\) 满足 \(x \le y\)。 \((x,y)\) 一定构成一条链,因为 \(x,y\) 一定是最小元或最大元之一,所以 \(S-(x,y)\) 一定会使反链大小减 \(1\),利用归纳结论,由此时的划分数量有 \(w-1\) 个,此时再加上 \((x,y)\) 恰好有 \(w\),结论成立。
画个图感觉有助于理解
我们考虑上图那个例子,\(A=\{2,3\},D(A)=\{1 \},U(A)=\{4,6,8,12 \}\),注意到 \(A \cup D(A)\) 可以划分为两条链,\(A \cup U(A)\) 可以划分为两条链且互相对应的两条链可以合并。
我们再考虑一个例子,上图假设只有 \(S=\{1,2,3 \}\) 三个点,\(U(A)\) 为空,考虑选取最大元 \(y=3\),最小元 \(x=1\) 注意到其构成一条链,删去这条链后,\(A\) 就删去了一个 \(3\),考虑 \(A\) 必然只会减少一个元素,这样利用归纳结论就证明完了。
主播主播还有没有更说人话的证明
我们考虑这样一个过程 \(A\) 中每个元素都向上向下走,尽可能多走,考虑一定能走完全图,否则就不是最大的反链,最后在 \(A\) 处合并,这样得到的一定是一种链的划分。
P1020 导弹拦截
对于第一问用线段树直接求就行了,对于第二问我们构造偏序集 \((S, R )\),\(R\) 表示 \(i \le j,a_i \le a_j\),\(a\) 是高度,用 Dilworth 定理转换相当于求严格上升子序列就做完了。
点击查看代码
// Problem: P1020 [NOIP 1999 提高组] 导弹拦截
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1020
// Author: Air2011
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// 循此苦旅,直抵群星
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define Air
namespace io{
inline int read(){
int f=1,t=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();}
return t*f;
}
inline double readlf(){
double f=1,t=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();}
if(ch=='.'){double dot=0.1;ch=getchar();while(ch>='0'&&ch<='9'){t=t+(ch-'0')*dot;ch=getchar();dot*=0.1;}}
return t*f;
}
inline void write(int x){
if(x<0){putchar('-');x=-x;}
if(x>=10)write(x/10);
putchar(x%10+'0');
}
inline void writestr(char *arr,int st=0){
int len=strlen(arr+st);
for(int i=st;i<len+st;i++) putchar(arr[i]);
}
}
using namespace io;
int n;
const int N=1e5+10,INF=5e4+10;
int a[N];
struct Segment{
int l,r;
int dat;
}seg[N*4];
void upd(int p){
seg[p].dat=max(seg[p*2].dat,seg[p*2+1].dat);
}
void build(int p,int l,int r){
seg[p].l=l;
seg[p].r=r;
seg[p].dat=0;
if(l==r)return ;
int mid=(l+r)>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void change(int p,int x,int v){
if(seg[p].l==seg[p].r){
seg[p].dat=max(seg[p].dat,v);
return ;
}
int mid=(seg[p].l+seg[p].r)>>1;
if(x<=mid){
change(p*2,x,v);
}
else{
change(p*2+1,x,v);
}
upd(p);
}
int ask(int p,int l,int r){
if(r<l)return 0;
if(seg[p].l>=l&&seg[p].r<=r){
return seg[p].dat;
}
int mid=(seg[p].l+seg[p].r)>>1;
int val=0;
if(l<=mid){
val=max(val,ask(p*2,l,r));
}
if(r>mid){
val=max(val,ask(p*2+1,l,r));
}
return val;
}
int dp[N];
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
while(cin>>a[n+1]){
n++;
}
int ans1=0;
build(1,1,INF);
for(int i=1;i<=n;i++){
dp[i]=ask(1,a[i],INF)+1;
ans1=max(ans1,dp[i]);
change(1,a[i],dp[i]);
}
write(ans1);
putchar('\n');
build(1,1,INF);
int ans2=0;
for(int i=1;i<=n;i++){
dp[i]=ask(1,1,a[i]-1)+1;
ans2=max(ans2,dp[i]);
change(1,a[i],dp[i]);
}
write(ans2);
return 0;
}

浙公网安备 33010602011771号