rt
/*
在下图中:A,C是友好城市,B,D是友好城市(有点丑)
A B B A
\ / \ /
\ / \ /
/\ /\
/ \ / \
D C C D
假设A->C已经被批准,那么另外任意一条B->D的航线,如果(B>A and D<C) 或 (B<A and D>C)[1],则不能被批准
注[1]:这里的A,B,C,D指的是四个点的南北坐标,A,C对应的点是友好城市、B,D对应的点是友好城市
发现要满足题目中的条件,需要保证南岸序列和北岸序列单调上升(题目中交代了坐标不会重合)。
我们可以按北岸坐标大小对城市进行排序,来保证北岸序列单调上升;
然后,南岸序列的最长上升子序列长度就是能够批准的航线条数。
朴素的求最长上升子序列复杂度为O(n^2),这里可以用贪心+二分优化成O(nlogn)
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 7;
struct Node{
int nor, sou; // no:north北岸的城市坐标;so:south南岸的城市坐标
}a[maxn];
bool cmp(Node p1, Node p2){
if(p1.nor != p2.nor) return p1.nor < p2.nor; // 先使得北岸升序
return p1.sou < p2.sou; // 再让南岸升序(这一条不加也可以)
}
int f[maxn]; // f[i]表示求出来的最长上升子序列中第i个位置的最优解(按照贪心的思路,肯定在某个位置的值尽可能小,留给后面的大小限制就更宽泛,所以位置i的值越小越优)
// 不难看出,f一定单调递增,具有单调性,所以能应用二分优化
void solve()
{
int n; cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i].nor >> a[i].sou;
sort(a + 1, a + n + 1, cmp);
f[1] = a[1].sou; // 第一个直接加入dp数组
int pos = 1; // pos指向f数组的末尾位置
for(int i = 2; i <= n; i ++){
if(f[pos] < a[i].sou){ // 如果它大于最后一个,说明它没有往前插的余地,直接加到最后面
f[++ pos] = a[i].sou;
}
else{ // 否则,在1~pos中找到第一个大于等于它的元素j,并将j替换为a[i].sou。这样可以给后面的元素“留下更为宽泛的限制”,更容易形成最长上升子序列。对答案更优
int l = 1, r = pos, j = -1; // j 是要找的答案
while(l <= r){
int mid = (l + r) >> 1;
if(j >= a[mid].sou){ // 符合条件,继续向前二分,找第一个符合条件的
j = mid;
r = mid - 1;
}else l = mid + 1;
}
f[j] = a[i].sou; // 前面已经特判过a[i].sou大于f最后一个的情况了,又因为f单调不降,所以这里不用担心找不到的情况
}
}
cout << pos << '\n'; // pos的值即为f数组的长度,也就是LIS的长度
}
signed main()
{
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}