**【杭电1025】LIS 二分

构建不交叉道路
 Constructing Roads In JGShining's Kingdom
Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u

Description

JGShining's kingdom consists of 2n(n is no more than 500,000) small cities which are located in two parallel lines. 

Half of these cities are rich in resource (we call them rich cities) while the others are short of resource (we call them poor cities). Each poor city is short of exactly one kind of resource and also each rich city is rich in exactly one kind of resource. You may assume no two poor cities are short of one same kind of resource and no two rich cities are rich in one same kind of resource. 

With the development of industry, poor cities wanna import resource from rich ones. The roads existed are so small that they're unable to ensure the heavy trucks, so new roads should be built. The poor cities strongly BS each other, so are the rich ones. Poor cities don't wanna build a road with other poor ones, and rich ones also can't abide sharing an end of road with other rich ones. Because of economic benefit, any rich city will be willing to export resource to any poor one. 

Rich citis marked from 1 to n are located in Line I and poor ones marked from 1 to n are located in Line II. 

The location of Rich City 1 is on the left of all other cities, Rich City 2 is on the left of all other cities excluding Rich City 1, Rich City 3 is on the right of Rich City 1 and Rich City 2 but on the left of all other cities ... And so as the poor ones. 

But as you know, two crossed roads may cause a lot of traffic accident so JGShining has established a law to forbid constructing crossed roads. 

For example, the roads in Figure I are forbidden. 



In order to build as many roads as possible, the young and handsome king of the kingdom - JGShining needs your help, please help him. ^_^ 

Input

Each test case will begin with a line containing an integer n(1 ≤ n ≤ 500,000). Then n lines follow. Each line contains two integers p and r which represents that Poor City p needs to import resources from Rich City r. Process to the end of file. 

Output

For each test case, output the result in the form of sample. 
You should tell JGShining what's the maximal number of road(s) can be built. 

Sample Input

2
1 2
2 1
3
1 2
2 3
3 1

Sample Output

Case 1:
My king, at most 1 road can be built.

Case 2:
My king, at most 2 roads can be built.
两个for直接超时
#include<cstdio>
#include<algorithm>
using namespace std;
struct node
{
	int x,y;
}a[500001];
int num[500001];
int main()
{
	int n;
	int k=1;
	while(~scanf("%lld",&n))
	{
	int  maxm=0;
	    for(int i=1;i<=n;i++)
	    {
		scanf("%d%d",&a[i].x,&a[i].y);
			num[i]=1;
	    	for(int j=1;j<i;j++)
	    	{
	    		if(a[i].x>a[j].x&&a[i].y>a[j].y)
	    		if(num[i]<=num[j]+1)
				num[i]=num[j]+1;	
			}
			maxm=max(maxm,num[i]);
		}
		printf("Case %d:\n",k++);
		if(maxm==1)
		printf("My king, at most %d road can be built.\n",maxm);
		else
		printf("My king, at most %d roads can be built.\n",maxm)
	}
	return 0;
}

看了别人的,才知道要用二分。╭∩╮(︶︿︶)╭∩╮  
详解:http://blog.youkuaiyun.com/lishuhuakai/article/details/8168328       
<span style="font-size:14px;">#include<iostream>
using namespace std;

long d[500001],a[500001];  //d[i]用来存最长不降子序列 最后一个元素 
long find(long a,long left,long right)  //二分查找
{
	long mid;
	while(left<=right)
	{
		mid=(left+right)/2;
       if(a>d[mid])//偏小 
		   left=mid+1;
	   else
		   right=mid-1;
	}
	return left;
}
int main()
{
	long n,i,j,len,p,t,k=0;
	while(scanf("%d",&n)!=EOF)
	{
		k++;
		for(i=1;i<=n;i++)
		{scanf("%d%d",&j,&p);
		a[j]=p;}
		d[1]=a[1];
		len=1;//初始序列长度 
		for(i=2;i<=n;i++)   
		{
			t=find(a[i],1,len);   //找出a[i]的位置
			d[t]=a[i];            //更新d[t],使d[t]尽量是最小的,显而易见,dp[t]越小,后面的dp[k](k>t)更大的可能性就会越大 
			//为什么可以这样做?仔细观察,发现对于一个a[i],要判断它是否是某个不降子序列的元素,我们只需要拿它和前面的dp[j](j<len)比较即可,
			//由于dp[j]记录的是到a[j]为止的序列的最长不降子序列的最后一个元素,如果a[i]>dp[j]并且a[i].b<dp[j+1],
			//那么我们自然要用a[i]更新dp[j+1](很明显,最长子序列长度为j+1),而如果a[i]比dp[j]的值都大,
			//这说明a[i]可以接在所有的dp[j]后面,自然我们选择最长的dp[len],因此,加入之后,len的长度要自加1  
            if(t>len)            //因为d[len]都是最大的
				len++;
		}
   if(len==1)
     printf("Case %d:\nMy king, at most %d road can be built.\n\n",k,len);  //单数
   else
     printf("Case %d:\nMy king, at most %d roads can be built.\n\n",k,len);  //复数
	}
	return 0;
}</span>

<think>我们使用贪心策略:希望上升子序列尽可能长,那么就要让序列上升得尽可能慢,即每次在子序列末尾添加的元素尽可能小。 维护一个数组 `d`,其中 `d[i]` 表示所有长度为 `i` 的上升子序列中末尾元素的最小值。注意:`d` 数组是严格递增的(因为如果长度为 `i` 的上升子序列的最小末尾比长度为 `i+1` 的还大,那么长度为 `i+1` 的那个子序列的前 `i` 个元素构成的子序列的末尾就会小于 `d[i]`,与定义矛盾)。 算法步骤: 1. 初始化:令 `d[1] = nums[0]`,长度 `len = 1`。 2. 遍历数组 `nums`(从第二个元素开始): - 如果当前元素 `nums[i]` 大于 `d[len]`,则直接加入 `d` 数组末尾,并且 `len++`。 - 否则,在 `d[1..len]` 中二分查找第一个大于等于 `nums[i]` 的位置 `pos`,并更新 `d[pos] = nums[i]`(因为我们要让长度为 `pos` 的上升子序列的末尾元素尽可能小,所以用更小的 `nums[i]` 替换掉原来的 `d[pos]`)。 注意:这里二分查找的是第一个大于等于 `nums[i]` 的位置(因为我们希望替换掉第一个比 `nums[i]` 大的元素,这样替换后序列仍然有序,且我们让这个位置的末尾元素变小了,为后续提供更多可能)。 最终 `len` 就是最长上升子序列的长度。 注意:这个算法只能得到最长上升子序列的长度,如果要还原出具体的序列,需要另外记录路径(但这里我们只求长度)。 为什么正确? 我们维护的 `d` 数组并不一定是真实存在的子序列,但是它的长度就是最长上升子序列的长度。因为每次我们扩展长度时(即遇到比末尾大的元素),就相当于在原来的某个上升子序列后面添加了一个元素,使得长度加一。而当我们替换某个位置的值时,我们并没有改变当前的长度,但是让这个位置的末尾元素更小了,这样在后续的遍历中,我们就有可能构造出更长的子序列。 时间复杂度:O(n log n) 下面给出C++实现:</think>### LIS贪心算法与二分查找实现详解 贪心+二分查找是求解最长上升子序列(LIS)问题的最优解法,时间复杂度为 $O(n \log n)$,空间复杂度为 $O(n)$。其核心思想是**维护一个最小末尾元素序列**,使序列尽可能缓慢增长。 #### 算法核心思想 1. **贪心策略**:让上升子序列增长尽可能慢,即每次添加的元素尽可能小 2. **状态定义**:`d[i]` 表示长度为 `i` 的上升子序列的最小末尾元素 3. **关键性质**:`d` 数组严格递增(证明:若 $d[i] \geq d[i+1]$,则 $d[i+1]$ 对应的序列前 `i` 个元素构成的子序列末尾元素 $< d[i]$,矛盾) 4. **二分优化**:利用 `d` 的有序性,通过二分查找确定插入位置 #### 算法流程 1. 初始化 `d[1] = nums[0]`, `len = 1` 2. 遍历数组元素: - 若 `nums[i] > d[len]`:扩展序列,`d[++len] = nums[i]` - 否则:二分查找 `d[1..len]` 中第一个 $\geq$ `nums[i]` 的位置 `pos`,更新 `d[pos] = nums[i]` 3. 最终 `len` 即为 LIS 长度 #### C++ 实现代码 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int lengthOfLIS(vector<int>& nums) { if (nums.empty()) return 0; int n = nums.size(); vector<int> d(n + 1, 0); // d数组,d[0]不使用 int len = 1; d[1] = nums[0]; for (int i = 1; i < n; i++) { if (nums[i] > d[len]) { // 扩展序列 d[++len] = nums[i]; } else { // 二分查找插入位置 int left = 1, right = len; while (left <= right) { int mid = left + (right - left) / 2; if (d[mid] < nums[i]) { left = mid + 1; } else { right = mid - 1; } } // 更新d数组 d[left] = nums[i]; } } return len; } int main() { vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18}; cout << "最长上升子序列长度: " << lengthOfLIS(nums) << endl; // 输出: 4 return 0; } ``` #### 关键步骤解析 1. **初始化**:`d[1] = nums[0]`,初始长度为1 2. **遍历处理**: - 当前元素 `7`:大于 `d[3]=5`,扩展序列:`d[4]=7`,`len=4` - 当前元素 `3`:小于 `d[3]=5`,二分查找位置2(`d[2]=3`),更新 `d[2]=3` 3. **二分查找**:在有序数组 `d[1..len]` 中定位第一个 $\geq$ 当前元素的位置 #### 复杂度分析 - **时间复杂度**:$O(n \log n)$ - 遍历 $n$ 个元素,每次二分查找 $O(\log n)$ - **空间复杂度**:$O(n)$(存储 `d` 数组) #### 算法正确性证明 - **维护最小末尾**:每次更新确保 `d[i]` 是长度为 `i` 的 LIS 的最小可能末尾 - **单调性保证**:$d[1] < d[2] < \cdots < d[len]$ 严格成立 - **长度最优**:最终 `len` 对应真实 LIS 长度(可通过数学归纳法证明) #### 扩展应用 1. **输出具体序列**:额外记录路径数组 `path`,回溯时从 `d[len]` 开始反向追踪[^1] 2. **非严格递增**:修改比较条件为 `nums[i] >= d[len]` 和 `d[mid] <= nums[i]` 3. **二维LIS问题**:如俄罗斯套娃问题(先按宽度排序,高度求LIS)[^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值