7-2 列车调度 (25 分)
火车站的列车调度铁轨的结构如下图所示。
两端分别是一条入口(Entrance)轨道和一条出口(Exit)轨道,它们之间有N
条平行的轨道。每趟列车从入口可以选择任意一条轨道进入,最后从出口离开。在图中有9趟列车,在入口处按照{8,4,2,5,3,9,1,6,7}的顺序排队等待进入。如果要求它们必须按序号递减的顺序从出口离开,则至少需要多少条平行铁轨用于调度?
输入格式:
输入第一行给出一个整数N
(2 ≤ N
≤105),下一行给出从1到N
的整数序号的一个重排列。数字间以空格分隔。
输出格式:
在一行中输出可以将输入的列车按序号递减的顺序调离所需要的最少的铁轨条数。
输入样例:
9
8 4 2 5 3 9 1 6 7
输出样例:
4
思路:
这个题还是挺有价值的。你仔细看看,会发现其实他在考你最长上升子序列问题(LIS)。关于什么是LIS,一会再说,在这里先介绍一下为啥这个题是在考LIS,这里就要用到一个定理,叫做Dilworth定理,这是组合数学中的一个定理。关于这个定理,我建议你就别百度搜是啥了,除非是大牛,一般人真看不懂。。。
我说的这个应该就够了,定理的大致意思是:
在一个序列中 最长下降子序列的个数就等于其最长不下降子序列的长度
举例:1 2 3 2 3
最长下降子序列:3 2-->长度为2
最长上升子序列:1 2 3-->长度为3
反之也一样。
哈哈好懂吧。
因为此题中没有重复值,所以其实就是求最长上升子序列的长度。
关于求解LIS问题的方法有两种,一种时间复杂度为O(n^2)还有一种为O(n log n),这个题得用第二种,用第一种肯定会超时。
关于这个LIS问题的详细说明,请参考博客:https://blog.csdn.net/weixin_42110638/article/details/83410664
相信看完你就懂了
具体答案如下:
#include<cstdio>
#include<algorithm>
#include <bits/stdc++.h>
const int MAXN=200001;
int a[MAXN];
int d[MAXN];
int find(int a[],int start,int end,int key)//二分查找,返回a数组中第一个>=key的位置
{
int left = start;
int right = end;
int mid;
while(left <= right)
{
mid=(left + right)/2;
if(d[mid] > key)
right = mid - 1;
else
left = mid + 1;
}
return left;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
d[1]=a[1];//代表下标都从0开始
int len=1;
for(int i=2;i<=n;i++)//用数组模拟栈
{
if(a[i]>=d[len])//每遇到一个比栈顶元素大的数,就放进栈里,
d[++len]=a[i];
else//遇到比栈顶元素小的就二分查找前边的元素
{
//int j=std::lower_bound(d+1,d+len+1,a[i])-d;//找到下标
int j = find(a,1,len,a[i]);//返回第一个大于等于key的元素
d[j]=a[i];//并用它替换掉栈顶元素
//if(len < j)
//len = j;
}
}
printf("%d\n",len);
return 0;
}
当然还可以使用一些STL的容器或者函数
补充两种用stl的方法:
原理:
Set不但自动排序,里面还有upper_bound(num)函数,查找与num最接近的比num大的数,省去了内层循环,时间复杂度缩小到了O(n)。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int num,n;
cin>>n;
set<int> s;
for(int i=0;i<n;i++)
{
cin>>num;
if(s.upper_bound(num)!=s.end())//如果找到了最接近num且比num大的数(==s.end代表没找到)
s.erase(s.upper_bound(num));//就把那个数删了(num照常插入)(更新过程)
s.insert(num);
}
cout<<s.size();
return 0;
}
或者使用lower_bound( begin,end,num)函数
在从小到大的排序数组中,
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
关于它的使用方法可参考链接:https://blog.csdn.net/qq_40160605/article/details/80150252
#include<cstdio>
#include<algorithm>
const int MAXN=200001;
int a[MAXN];
int d[MAXN];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
d[1]=a[1];
int len=1;
for(int i=2;i<=n;i++)
{
if(a[i]>d[len])
d[++len]=a[i];
else
{
int j=std::lower_bound(d+1,d+len+1,a[i])-d;
d[j]=a[i];
}
}
printf("%d\n",len);
return 0;
}