一中OJ | #1438 航线设计
时限 1000MS/Case 内存 64MB/Case
题目描述
有一个国家被一条河划分为南北两部分,在南岸和北岸总共有N对城镇,每一城镇在对岸都有唯一的友好城镇。任何两个城镇都没有相同的友好城镇。每一对友好城镇都希望有一条航线来往。于是他们向政府提出了申请。由于河终年有雾。政府决定不允许有任两条航线交叉(如果两条航线交叉,将有很大机会撞船)。
你的任务是写一个程序来帮政府官员决定他们应拨款兴建哪些航线以使得没有出现交叉的航线最多。
输入格式
第一行一个整数N,表示分布在河两岸的城镇对数。接下来的N行每行有两个由空格分隔的正数C,D(C、D<=10^9〉,描述每一对友好城镇沿着河岸与西边境线的距离,C表示北岸城镇的距离而D表示南岸城镇的距离。在河的同一边,任何两个城镇的位置都是不同的。
输出格式
在安全条件下能够开通的最大航线数目。
样例输入
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
样例输出
4
数据范围
1<=N<=500000
----------------------------------------------------------
题目分析
将上下两端看成匹配,假设有航线A<-->B和航线C<-->D,它们不交叉的条件是AB的不等关系与CD相同(当A<B时C<D 或 当A>B时C>D)
那么将每条航线按北端点进行排序,在搜索A的时候,A一定是小于A后面的所有元素的,那么这个时候只需要找一个大于C的D即可。
因为北端点已经有序了,那么只需要对南端点的序列找LIS就可以保证一定不会相交啦
----------------------------------------------------------
关于NlogN找LIS的方法
网上已经有无数篇介绍这个方法的啦,但是还是说一下吧
设数组d储存当前的最小字典序的LIS序列
也就是d[i]代表当前长度为i的LIS的第i位的最小值
动态维护d数组,假设当前最长长度为ans,那么当a[i+1]大于d[ans]的时候把d[ans+1]改成a[i],表示找到了长度为ans+1的LIS,ans+1
当a[i+1]不大于d[ans]的时候二分找a[i+1]能排到什么位置,然后与d[x]替换,即可保证d数组一定是字典序最小的LIS序列
更详细的解释来自我写代码的时候敲的注释::
/*
设d[i]为长度为i的LIS的末尾元素的最小值
对d[i]进行维护使得d[i]保持单调递增性
设当前DP找到的LIS长度为i,当找长度为i+1的LIS的时候就可以使用二分查找来找比d[i]大的最小值
那么如何对一个没有排序的序列进行二分查找呢?这显然是不现实的
其实只需要对d进行二分查找,找到的i就是以当前a[x]为末尾的LIS长度
此时为了保证d[i]是长度为i的LIS的末尾元素的最小值,对d[i]进行更新,d[i]=min(d[i],当前数字)
如果二分查找的结果是d数组的末尾+1,说明当前数字可以插入LIS中,再把d[i]更新成当前数字即可
------
特别注意,{d[i] | i>0}的初始值应该为inf,这样可以保证无论如何都可以替换d[i]
*/
----------------------------------------------------------
代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#define hashsize 1000003
#define inf 0x7f7f7f7f
using namespace std;
struct node
{
friend bool operator <(node a,node b)
{
return a.north<b.north;
}
int north;
int south;
}line[500005];
int d[500005],a[500005],ans=0,n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&line[i].north,&line[i].south);
sort(line+1,line+1+n);
for(int i=1;i<=n;i++) a[i]=line[i].south;
for(int i=1;i<=n;i++) d[i]=inf;
d[0]=-inf;
for(int i=1;i<=n;i++)
{
int p=upper_bound(d+1,d+ans+1,a[i])-d;
//当p为ans+1时,说明当前数字可以插入LIS
if(p==ans+1)
{
ans++;
d[ans]=a[i];
}
else d[p]=min(d[p],a[i]);
}
cout<<ans;
return 0;
}