两端分别是一条入口(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
思路,注意几个点:
- 列车编号无重复
- 列车必须按编号由大到小出来
- 列车进入某一条轨道时,只需判断该列车编号和该轨道上最后进入的列车的编号关系即可
故每次遍历已开放的轨道,如果当前列车编号x[i]小于当前轨道上最后进入的列车的编号a[j],即说明列车可以进入当前轨道,因为这样才能保证编号大的一定比编号小的先出轨道。
当有多条可选轨道时,要选择最后进入列车的编号最小的那个轨道,这样才能保证最小代价,使得开放轨道数最小。
代码1:
#include <stdio.h>
/**
列车调度 无二分
**/
//x[i]表示第i躺列车的编号, a[i]表示轨道i上z最后进入的列车的编号
int a[100005],x[100005];
int main()
{
int n,cnt=0;
int k,mi;
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&x[i]);
for(int i=0; i<n; i++)
{
/**贪心选择 选取最优轨道**/
mi = 100005;
k = -1;
for(int j=0; j<cnt; j++)
{
if(x[i]<a[j]&&a[j]-x[i]<mi)//选择能进入的且代价最小的轨道
{
mi = a[j]-x[i];
k = j;
}
}
/**列车入轨**/
if(k==-1)//若不能进入任何一条已开放轨道, 则开辟一条新轨道
a[cnt++] = x[i];
else//列车x[i]进入轨道k, 更新a[k]
a[k] = x[i];
}
printf("%d\n",cnt);
return 0;
}
不难发现,这样的时间复杂度为 O(n2),而此题n最大为 105,故循环次数最大将到 (105)2=1010,而该题的时间限制只有300ms,一般而言 108 次循环将达到1s左右的时间,所以显然,这里将会超时,应进行优化。
不难发现,数组a总是有序的,即可以通过二分搜索将里层循环优化至 log2(n),这样一来时间复杂度变为 O(nlog2(n))
≈
\approx
≈ 106,这样便不会超时了。
代码2:
#include <stdio.h>
/**
列车调度 二分法
**/
int a[100005],x[100005];
int binarySearch(int i,int cnt)
{
//二分搜索
//在数组a[]中查找大于x[i]的最小下标j,使得a[j]>x[i]
if(cnt==0)
return 0;//没有轨道,直接新开轨道0
int left=0, right=cnt-1, middle=0;//初始二分搜索区间为整个轨道数组
while(left<=right)//二分搜索循环出口
{
middle = (left+right)/2;//取得中间元素
if(x[i]<a[middle])
right = middle-1;//若小于中间元素, 则继续二分搜索左区间
else
left = middle+1;//若大于中间元素, 则继续二分搜索右区间
}
return left;//返回下标, 不难证明, 循环直至left>right时, left即为所求下标(此处较难理解, 建议找几个简单例子模拟一下过程)
}
int main()
{
int n,cnt=0;
int k;
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&x[i]);
for(int i=0; i<n; i++)
{
k = binarySearch(i,cnt);//二分搜索
a[k] = x[i];//将列车x[i]入轨道a[k], 不难证明, 由于列车编号不重复, 修改后的轨道数组a仍是有序的
if(k>=cnt)//若k>=cnt, 则说明是开辟新轨道, 故cnt++
cnt++;
}
printf("%d\n",cnt);
return 0;
}
答案正确