试题题目:
本题为编程题第四题
解题思路:
1、分析一
首先看到了排序,根据题目意思:
<1>、当输入
(
0
(0
(0
S
e
a
t
)
Seat)
Seat)时,将数列从
a
1
a_1
a1到
a
S
e
a
t
a_{Seat}
aSeat进行降序排列;
<2>、当输入
(
1
(1
(1
S
e
a
t
)
Seat)
Seat)时,将数列从
a
S
e
a
t
a_{Seat}
aSeat到
a
n
a_n
an进行升序排列;
利用快速排序算法:
Q
u
i
c
k
s
o
r
t
(
i
n
t
Quicksort(int
Quicksort(int
∗
a
,
i
n
t
*a,int
∗a,int
l
e
f
t
,
i
n
t
left,int
left,int
r
i
g
h
t
,
i
n
t
right,int
right,int
t
y
p
e
)
type)
type),测试代码可以看到运行超时,对于数列数以及操作数量非常大时,显然不合适。对于排序算法,已经无法实现优化了,我们只能对输入的数据进行优化。代码如下:
#include<stdio.h>
int N[1000005];
void Quicksort(int *,int,int,int);
int main(){
int Length,Times,Type,Seat;
int i,j,L,R;
scanf("%d %d",&Length,&Times);
for(i=1; i<=Length; i++){ N[i] = i;}
for(i=0; i<Times; i++){
scanf("%d %d",&Type,&Seat);
Type == 1 ? ( L = Seat, R = Length ) : ( L = 1, R = Seat );
Quicksort(N,L,R,Type);
}
for(j=1;j<=Length;j++){
printf("%d ",N[j]);
}
return 0;
}
2、分析二
需要对输入数据进行分析,应当考虑输入数据的规律、数量、种类、方式等特性,根据特性优化程序。对于本题目分析发现输入数据是存在着某种规律的,用于简化实际对序列的操作步骤。分析规律如下:
<1>、当输入数据的操作类型为连续的
1
1
1(升序)时,应当去其中的参数最小的那一个操作,将本组操作合并成了一个有效操作。
解释:
(
1
,
a
3
)
(1,a_3)
(1,a3) 操作,将序列从
A
r
r
y
a
3
Arry_{a_3}
Arrya3到
A
r
r
y
n
Arry_n
Arryn升序排列;
(
1
,
a
1
)
(1,a_1)
(1,a1) 操作,将序列从
A
r
r
y
a
1
Arry_{a_1}
Arrya1到
A
r
r
y
n
Arry_n
Arryn升序排列;
(
1
,
a
2
)
(1,a_2)
(1,a2)操作,将序列从
A
r
r
y
a
2
Arry_{a_2}
Arrya2到
A
r
r
y
n
Arry_n
Arryn升序排列; 在
a
1
<
a
2
<
a
3
a_1<a_2<a_3
a1<a2<a3的情况下,连续的三个操作,等价于一个操作
(
1
,
a
1
)
(1,a_1)
(1,a1) 。
<2>、当输入数据的操作类型为连续的
0
0
0(降序)时,应当去其中的参数最大的那一个操作,将本组操作合并成了一个有效操作。
解释:
(
0
,
a
1
)
(0,a_1)
(0,a1) 操作,将序列从
A
r
r
y
1
Arry_1
Arry1到
A
r
r
y
a
1
Arry_{a_1}
Arrya1降序排列;
(
0
,
a
3
)
(0,a_3)
(0,a3) 操作,将序列从
A
r
r
y
1
Arry_1
Arry1到
A
r
r
y
a
3
Arry_{a_3}
Arrya3降序排列;
(
0
,
a
2
)
(0,a_2)
(0,a2)操作,将序列从
A
r
r
y
1
Arry_1
Arry1到
A
r
r
y
a
2
Arry_{a_2}
Arrya2降序排列; 在
a
1
<
a
2
<
a
3
a_1<a_2<a_3
a1<a2<a3的情况下,连续的三个操作,等价于一个操作
(
0
,
a
3
)
(0,a_3)
(0,a3) 。
<3>、当输入数据的操作类型为交替的,第一个操作类型为
0
0
0(降序),第二个操作类型为
1
1
1(升序),第三个操作类型为
0
0
0(降序)。如果第三个参数大于等于第一个参数时,那么第一个操作和第二个操作均为无效操作。
解释:第一步
(
0
,
a
1
)
(0,a_1)
(0,a1) 操作,将序列从
A
r
r
y
1
Arry_1
Arry1到
A
r
r
y
a
1
Arry_{a_1}
Arrya1降序排列;第二步
(
1
,
a
2
)
(1,a_2)
(1,a2) 操作,将序列从
A
r
r
y
a
2
Arry_{a_2}
Arrya2到
A
r
r
y
n
Arry_n
Arryn升序排列;第三步
(
0
,
a
3
)
(0,a_3)
(0,a3)操作,将序列从
A
r
r
y
1
Arry_1
Arry1到
A
r
r
y
a
3
Arry_{a_3}
Arrya3降序排列; 在
a
1
<
a
2
<
a
3
a_1<a_2<a_3
a1<a2<a3的情况下,连续的三个操作,等价于一个操作
(
0
,
a
3
)
(0,a_3)
(0,a3) 。即当类型为
0
0
0时,本次输入的参数需要与上一个同类型的参数对比,大于等于它时,上两步操作无效。
<4>、当输入数据的操作类型为交替的,第一个操作类型为
1
1
1(升序),第二个操作类型为
0
0
0(降序),第三个操作类型为
1
1
1(升序)。如果第三个参数小于等于第一个参数时,那么第一个操作和第二个操作均为无效操作。
解释:第一步
(
1
,
a
3
)
(1,a_3)
(1,a3) 操作,将序列从
A
r
r
y
a
3
Arry_{a_3}
Arrya3到
A
r
r
y
n
Arry_n
Arryn升序排列;第二步
(
0
,
a
2
)
(0,a_2)
(0,a2) 操作,将序列从
A
r
r
y
1
Arry_1
Arry1到
A
r
r
y
a
2
Arry_{a_2}
Arrya2降序排列;第三步
(
1
,
a
1
)
(1,a_1)
(1,a1)操作,将序列从
A
r
r
y
a
1
Arry_{a_1}
Arrya1到
A
r
r
y
n
Arry_n
Arryn升序排列; 在
a
1
<
a
2
<
a
3
a_1<a_2<a_3
a1<a2<a3的情况下,连续的三个操作,等价于一个操作
(
1
,
a
1
)
(1,a_1)
(1,a1) 。即当类型为
1
1
1时,本次输入的参数需要与上一个同类型的参数对比,小于等于它时,上两步操作无效。
<5>、观察发现,初始的序列
A
r
r
y
1
Arry_1
Arry1、
A
r
r
y
2
Arry_2
Arry2、
A
r
r
y
3
Arry_3
Arry3…
A
r
r
y
n
Arry_n
Arryn,一定是一个升序列,相当于操作了
(
1
,
1
)
(1,1)
(1,1)。那么首次输入操作时,本次操作应当与该操作作比较。
便于理解见下表:
1 | 2 | 3 | 4 | |
---|---|---|---|---|
第一个操作 | ( 1 , a 3 ) (1,a_3) (1,a3) | ( 0 , a 1 ) (0,a_1) (0,a1) | ( 0 , a 1 ) (0,a_1) (0,a1) | ( 1 , a 3 ) (1,a_3) (1,a3) |
第二个操作 | ( 1 , a 1 ) (1,a_1) (1,a1) | ( 0 , a 3 ) (0,a_3) (0,a3) | ( 1 , a 2 ) (1,a_2) (1,a2) | ( 0 , a 2 ) (0,a_2) (0,a2) |
第三个操作 | ( 1 , a 2 ) (1,a_2) (1,a2) | ( 0 , a 2 ) (0,a_2) (0,a2) | ( 0 , a 3 ) (0,a_3) (0,a3) | ( 1 , a 1 ) (1,a_1) (1,a1) |
有效操作 | ( 1 , a 1 ) (1,a_1) (1,a1) | ( 0 , a 3 ) (0,a_3) (0,a3) | ( 0 , a 3 ) (0,a_3) (0,a3) | ( 1 , a 1 ) (1,a_1) (1,a1) |
2、程序设计
<1>、设计一个结构体数组,存储每一步的实际操作。初始时,结构体数组第0项,类型和参数均为
1
1
1,用指针
T
o
p
Top
Top 指向即将输入的结构体数组项数。
<2>、输入第一行,确定序列长度与操作次数。
<3>、循环输入数据。输入时首先与上一步操作比较:类型相同时,比较参数大小,写入上一步操作的参数中;类型不相同时,写入
T
o
p
Top
Top指向的数组位置。当
T
o
p
Top
Top大于等于
2
2
2时,需要向上合并,与
T
o
p
−
2
Top-2
Top−2项的参数作比较,考虑本次输入是否使得上两步操作无效,无效时本次操作写入到
T
o
p
−
2
Top-2
Top−2项位置中,直到已经输入的操作均有效时,进行下一步输入操作,本次操作输入到
T
o
p
−
2
Top-2
Top−2项位置中。
<4>、当结构体数组中存 储的都是有效操作时。可以发现,操作类型
1
1
1与
0
0
0交替,参数在类型为
1
1
1时递增,参数在类型为
0
0
0时递减。而且首次操作一定时
0
0
0,因为初始序列时操作为
1
1
1。
<5>、输入的操作数据完成后,可以完成排序任务了。参数在类型为
1
1
1时递增,参数在类型为
0
0
0时递减,说明每一次真正需要排序的序列区间在不断的缩小,为
0
0
0时改变右端点,为
1
1
1时改变左端点。又类型
1
1
1与
0
0
0交替,说明本次排序在上一次排序结果后,反转区间数据顺序即可。
#include<stdio.h>
#define SIZE 100005
int MAX(int,int);
int MIN(int,int);
void Reserse(int *,int,int);
struct Operation{
int O_Type;
int O_Seat;
}O[SIZE];
int arry[SIZE];
int main()
{
int i,j,top = 1;
int Length,Times,Type,Seat;
scanf("%d %d",&Length,&Times);
for(i=1;i<=Length;i++){ arry[i] = i; } //初始化序列
O[0].O_Type = 1, O[0].O_Seat = 1;
for(i=1;i<=Times;i++){
scanf("%d %d",&Type,&Seat);
//同类型比较参数
if( Type == O[top-1].O_Type ){
if(Type == 1){
O[top-1].O_Seat = MIN(O[top-1].O_Seat , Seat); //升序情况下寻找最小的位置
}
if(Type == 0){
O[top-1].O_Seat = MAX(O[top-1].O_Seat , Seat); //降序情况下寻找最大的位置
}
top--;
}
else{
O[top].O_Type = Type, O[top].O_Seat = Seat;
}
//向上合并
while(top-2 >= 0){
if((Type == 0 && Seat >= O[top-2].O_Seat) || (Type == 1 && Seat <= O[top-2].O_Seat)){
O[top-2].O_Seat = Seat;
top = top - 2;
}
else{
break;
}
}
top++;
}
//定区间
int Left = 1, Right = Length;
for(i=1;i<top;i++){
O[i].O_Type == 0 ? (Right = O[i].O_Seat) : (Left = O[i].O_Seat) ;
Reserse(arry,Left,Right);
}
for(i=1;i<=Length;i++){
printf("%d ",arry[i]);
}
return 0;
}
int MAX(int Value1,int Value2){ return Value1 > Value2 ? Value1 : Value2;}
int MIN(int Value1,int Value2){ return Value1 < Value2 ? Value1 : Value2;}
void Reserse(int a[],int L,int R){
while( L < R){
int temp = arry[L];
arry[L] = arry[R];
arry[R] = temp;
L++; R--;
}
}
2、优化程序
<1>根据数学常识,自然数
0
0
0 ~
n
n
n是奇偶数交替的,用奇偶性质便可以替代类型
0
0
0与
1
1
1,那么就可以省去结构体。设定初始时的时候操作为
O
p
e
r
[
0
]
=
1
Oper[0]=1
Oper[0]=1。注意:这里偶数表示升,奇数表示降。
<2>在输入操作数据时,首先判断类型
(1). 当
t
o
p
top
top指向了偶数,那么输入了
0
0
0类型操作,就与上一步相同了,需比较大小,参数填入上一步数组中;那么输入了
1
1
1类型操作,就要循环与
T
o
p
−
2
Top-2
Top−2 步操作参数作比较,输入的参数大于等于
T
o
p
−
2
Top-2
Top−2 步操作参数,则上两步参数无效,
T
o
p
Top
Top 退两步。
(2). 同理,当
t
o
p
top
top指向了奇数,那么输入了
1
1
1类型操作,就与上一步相同了,需比较大小,参数填入上一步;那么输入了
0
0
0类型操作,就要循环与
T
o
p
−
2
Top-2
Top−2 步操作参数作比较,输入参数小于等于
T
o
p
−
2
Top-2
Top−2 步操作参数,则上两步参数无效,
T
o
p
Top
Top 退两步。
<3>上一套代码知道,排序的区间在不断地向中间缩小,那么非区间范围,一定在每一次操作后,都有被固定的数值。
(1). 当操作为
0
0
0类型时,是将序列从
A
r
r
y
1
Arry_1
Arry1 到
A
r
r
y
O
p
e
r
[
i
]
Arry_{Oper[i]}
ArryOper[i] 降序排列,那么从
A
r
r
y
O
p
e
r
[
i
]
Arry_{Oper[i]}
ArryOper[i] 到
A
r
r
y
n
Arry_n
Arryn 数据就会被固定下来,后续也没有有效操作对其有影响。
(2). 当操作为
1
1
1类型时,是将序列从
A
r
r
y
O
p
e
r
[
i
]
Arry_{Oper[i]}
ArryOper[i] 到
A
r
r
y
n
Arry_n
Arryn 升序排列,那么从
A
r
r
y
1
Arry_1
Arry1 到
A
r
r
y
O
p
e
r
[
i
]
Arry_{Oper[i]}
ArryOper[i] 数据就会被固定下来,后续也没有有效操作对其有影响。
(3). 利用左右指针,分别向中间靠拢。为序列填入数据
V
a
l
u
e
Value
Value,填入的个数与每一步操作的参数有关,
V
a
l
u
e
Value
Value从序列长度开始递减。
(4). 完整数据输入完成后,当
t
o
p
top
top 为偶数时,操作为奇数个,最后一步操作为降序,指针应该从从
L
e
f
t
Left
Left 一位一位逐步指向
R
i
g
h
t
Right
Right ,用来填入数据;当
t
o
p
top
top 为奇数时,操作为偶数个,最后一步操作为升序,指针应该从
R
i
g
h
t
Right
Right 一位一位逐步指向
L
e
f
t
Left
Left,用来填入数据。
#include<stdio.h>
#define SIZE 100005
int MAX(int,int);
int MIN(int,int);
int Oper[SIZE]; //偶数操作为1 奇数的操作为0 值为位置
int arry[SIZE];
int main()
{
int i,top = 1;
int Length,Times,Type,Seat;
scanf("%d %d",&Length,&Times);
Oper[0] = 1;
while(Times--){
scanf("%d %d",&Type,&Seat);
if(Type){
if(top%2 == 1){
Seat = MIN(Oper[top-1],Seat);
top--;
}
while(top-2 >= 0 && Seat <= Oper[top-2]){ top -= 2;}
}
else{
if(top%2 == 0){
Seat = MAX(Oper[top-1],Seat);
top--;
}
while(top-2 >= 0 && Seat >= Oper[top-2]){ top -= 2;}
}
Oper[top++] = Seat;
}
int Left = 1, Right = Length;
int Value = Length;
for(i=1;i<top;i++){
if(i%2 == 1){
while(Right > Oper[i] && Left <= Right){
arry[Right--] = Value--;
}
}else{
while(Left < Oper[i] && Left <= Right){
arry[Left++] = Value--;
}
}
if(Left > Right) { break; }
}
if(top%2){
while(Left <= Right){
arry[Right--] = Value--;
}
}else{
while(Left <= Right){
arry[Left++] = Value--;
}
}
for(i=1;i<=Length;i++)
printf("%d ",arry[i]);
return 0;
}
int MAX(int Value1,int Value2){ return Value1 > Value2 ? Value1 : Value2;}
int MIN(int Value1,int Value2){ return Value1 < Value2 ? Value1 : Value2;}