题目的描述
1、朴素版题意描述
背景设定
Palmia 国东西走向的大河分南北两岸,两岸各分布 N 个坐标互不重复的城市;北岸每个城市唯一对应南岸一个友好城市,配对关系两两不重复。每一对友好城市申请修建一条跨河直线航道,要求获批的航道任意两条都不能交叉,需要挑选尽可能多的航道通过审批,求最多可批准的航道条数。
输入规则
- 首行输入整数 N,代表城市总对数;
- 后续 N 行,每行输入两个整数,依次为一组友好城市里南岸坐标、北岸坐标。
任务要求
输出满足航道互不交叉前提下,最多能批准的申请数量。
2、数学描述
1. 符号定义
设共有N组友好城市配对,构造配对集合:
T={(si,ni), i=1,2,...,N}T=\{(s_i,n_i),\ i=1,2,\dots,N\}T={(si,ni), i=1,2,...,N}
- sis_isi:第i组配对的南岸城市横坐标,所有sis_isi取值互异;
- nin_ini:第i组配对的北岸城市横坐标,所有nin_ini取值互异; 配对映射si↦nis_i \mapsto n_isi↦ni为一一映射。
2. 约束条件
选取子集T′⊆TT'\subseteq TT′⊆T,对T′T'T′内任意两组不同配对(sa,na),(sb,nb)(s_a,n_a),(s_b,n_b)(sa,na),(sb,nb),二者连接而成的线段(航道)无交点。
3. 优化目标
最大化子集T′T'T′包含的元素个数∣T′∣|T'|∣T′∣,输出该最大数值。
3、数据范围
1≤N≤5000,1≤N≤5000,1≤N≤5000,
0≤xi≤100000≤x_i≤100000≤xi≤10000
题目的分析
我们先上结论,再上证明。
1、结论
- 输入N组坐标:每组依次给出南岸城市横坐标、北岸友好城市横坐标;
- 先按南岸城市坐标从小到大 给所有配对排序;
- 排序后提取对应北岸城市的坐标序列,问题等价于 找这个序列里最长上升子序列的长度:
- 南岸坐标递增排布时,北岸坐标也单调递增,航道就不会交叉;北岸坐标递减则航道交叉;
- 最长上升子序列的长度就是最多可批准的航道数量。
2、证明
大河水平延伸,南岸在下、北岸在上,横坐标从左到右增大(越靠右 x 坐标越大):
- 南岸点记S、北岸点记N,一对友好城市连线段SN就是航道;
- 取两组航道:航道 1(Sa,Na)(S_a,N_a)(Sa,Na)、航道 2(Sb,Nb)(S_b,N_b)(Sb,Nb),强制约束SaS_aSa在南岸左侧、SbS_bSb在南岸右侧,即sa<sb\boldsymbol{s_a < s_b}sa<sb(南岸横坐标严格左小右大),只分两种北岸位置情况画图对比。
情况1:sa<sbs_a<s_bsa<sb且na<nbn_a<n_bna<nb(北岸左小右大)

南岸靠左的城市连北岸靠左城市,南岸靠右的城市连北岸靠右城市,线段走向一致,不会相交。
情况2:sa<sbs_a<s_bsa<sb且na>nbn_a>n_bna>nb(北岸左大右小)

南岸靠左的点连北岸靠右的点、南岸靠右的点连北岸靠左的点,线段必然相交。
严格逻辑证明(结合图形结论)
步骤 1:排序固定南岸顺序
把所有配对按南岸横坐标升序重排,得到固定顺序:
s1<s2<s3<⋯<sNs_1<s_2<s_3<\dots<s_Ns1<s2<s3<⋯<sN 此时只需观察对应北岸序列 A={n1,n2,...,nN}A=\{n_1,n_2,\dots,n_N\}A={n1,n2,...,nN}的大小关系。
步骤 2:多选航道的无交叉约束
若同时选中下标i<ji<ji<j的两条航道,天然满足si<sjs_i<s_jsi<sj; 由图形结论:想要航道不交叉,必须强制ni<njn_i<n_jni<nj。 推广到多条航道:选中一批下标i1<i2<⋯<iki_1<i_2<\dots<i_ki1<i2<⋯<ik,全部两两无交叉 ⟺ \iff⟺
ni1<ni2<⋯<nikn_{i_1}<n_{i_2}<\dots<n_{i_k}ni1<ni2<⋯<nik 这恰好就是上升子序列的定义。
步骤 3:最大值等价结论
题目要求选中航道数量最多,等价于找满足下标递增、数值递增的最长子序列,即序列A的最长上升子序列 (LIS) 长度。
3、闫氏DP分析法
我们可以从上方的结论得到,这道题本质还是最长上升子序列,只不过要固定南岸的自变量,即:把所有城市配对按南岸坐标sis_isi从小到大排序 ,南岸坐标成为严格递增的自变量,顺序完全锁定,不会变动;此时每组配对唯一对应一个北岸坐标nin_ini,得到一维序列{n1,n2,...,nN}\{n_1,n_2,\dots,n_N\}{n1,n2,...,nN}。
所以,我们就把这道题转换成求"自变量"固定后的"因变量"的最长上升子序列。
首先根据经验,这种题一般只有一维状态。
我们可以设:dpidpidpi
集合:所有朝同一方向且以nin_ini结尾的上升子序列的集合
属性:Max
即:所有朝同一方向且以nin_ini结尾的上升子序列的长度集合的最大值
然后状态计算,开始划分集合,我们以最后一个集合dpi来划分
显然集合可以划分为从{dp0,dp1,dp2,...,dpi−1}\{dp0,dp1,dp2,...,dpi-1\}{dp0,dp1,dp2,...,dpi−1}到达dpidpidpi的所有长度(要满足:nj<ni,j∈{j∣0≤j<i}n_j<n_i,j∈\{j|0≤j<i\}nj<ni,j∈{j∣0≤j<i}),因为dpidpidpi的定义是以nin_ini结尾的最长上升子序列的长度,因此dpi=max({dp0,dp1,dp2,...,dpi−1})+1,(nj<ni,j∈{j∣0≤j<i})dpi=max(\{dp0,dp1,dp2,...,dpi-1\})+1,(n_j<n_i,j∈\{j|0≤j<i\})dpi=max({dp0,dp1,dp2,...,dpi−1})+1,(nj<ni,j∈{j∣0≤j<i}),即:dpi=max{dpj+1∣0≤j<i, nj<ni}dpi = \max\big\{dpj + 1 \mid 0 \le j < i,\ n_j < n_i\big\}dpi=max{dpj+1∣0≤j<i, nj<ni},这也是我们本次动态规划的状态转移方程。
即:dpi=max{dpj+1∣0≤j<i, nj<ni}dpi = \max\big\{dpj + 1 \mid 0 \le j < i,\ n_j < n_i\big\}dpi=max{dpj+1∣0≤j<i, nj<ni}
闫氏DP分析图

题目的代码
**题目地址:**https://www.acwing.com/problem/content/1014/
cpp
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 5010;
int n;
PII city[N];
int f[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
scanf("%d%d", &city[i].first, &city[i].second);
// 最重要的一步排序,按照pair自带排序规则,先排first,若first相等再排second
sort(city, city + n);
int res = 0;
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
for (int j = 0; j < i; j ++ )
// 根据"因变量"找到最长上升子序列的长度
if (city[i].second > city[j].second)
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
// 输出最多能批准的申请数量
printf("%d\n", res);
return 0;
}
时间复杂度: O(n2)O(n^2)O(n2)
空间复杂度: O(n)O(n)O(n)