3.2
编程题
2
试题编号
:
2023-09-23-06-C-02
试题名称
:小杨的握手问题
时间限制
:
1.0 s
内存限制
:
128.0 MB
3.2.1
问题描述
小杨的班级里共有N 名同学,学号从 0至N-1。
某节课上,老师安排全班同学进行一次握手游戏,具体规则如下:老师安排了一个顺序,让全班 N名同学依次进入 教室。每位同学进入教室时,需要和
已经在教室内
且
学号小于自己
的同学握手。
现在,小杨想知道,整个班级总共会进行多少次握手。
提示:可以考虑使用归并排序进行降序排序,并在此过程中求解。
3.2.2
输入描述
输入包含2 行。第一行一个整数N ,表示同学的个数;第二行N 个用单个空格隔开的整数,依次描述同学们进入教 室的顺序,每个整数在0到N-1 之间,表示该同学的学号。
保证每位同学会且只会进入教室一次。
3.2.3
输出描述
输出一行一个整数,表示全班握手的总次数。
3.2.4
特别提醒
在常规程序中,输入、输出时提供提示是好习惯。但在本场考试中,由于系统限定,请不要在输入、输出中附带任 何提示信息。
3.2.5
样例输入
1
42 1 3 0
3.2.6
样例输出
1
2
3.2.7
样例解释
1
2号同学进入教室,此时教室里没有其他同学。
1号同学进入教室,此时教室里有 2号同学。 1号同学的学号小于2 号同学,因此他们之间不需要握手。
3号同学进入教室,此时教室里有1,2号同学。 3号同学的学号比他们都大,因此 3号同学需要分别和另外两位同学 握手。
0号同学进入教室,此时教室里有1,2,3号同学。 0号同学的学号比他们都小,因此0 号同学不需要与其他同学握 手。
综上所述全班一共握手2次。
3.2.8
样例输入
2
60 1 2 3 4 5
3.2.9
样例输出
2
15
3.2.10
样例解释
2
全班所有同学之间都会进行握手,因为每位同学来到教室时,都会发现他的学号是当前教室里最大的,所以他需要 和教室里的每位其他同学进行握手。
3.2.11
数据规模
对于30%的测试点,保证N<=100。
对于所有测试点,保证2<=N<=3x10^5。
解析:
对于每一个进入教室的同学,都要计算教室内比他学号小的同学有多少个,累加即可。
1.如果我们采用模拟的办法,做个插入排序,每次确定当前同学在教室中所有同学中的顺序,则时间复杂度为O(N^2),只能解决30%的数据,详见代码:
#include <iostream>
using namespace std;
int num[300005];
long long ans=0;
int main() {
int n = 0;
cin >> n;
for (int i = 0; i < n; i++){
cin >> num[i];
for(int j=i;j>0;j--){
if (num[j]<num[j-1]){//模拟插入排序
swap(num[j],num[j-1]);
}else{
ans+=j;//有j个同学学号比当前同学小
cout<<j<<endl;
break;
}
}
}
cout<<ans;
return 0;
}
再仔细分析一下,这个题实际是求整个数组中顺序对的个数,参考求逆序对,我们可以使用归并排序,算出所有顺序对,即采用归并排序,将数组按从大到小排列,计算其中的逆序对,即为答案,详见代码:
#include <iostream>
using namespace std;
int num[300000];
int tmp[300000];
//归并排序
long long merge(int l, int r) {
if (l + 1 == r)
return 0;
int m = (l + r) / 2;
//先分
long long res = merge(l, m) + merge(m, r);
//后合
for (int i = l, j = m, k = l; k < r; k++) {
//左边数组中的学号大,直接排
if (j == r || (i < m && num[i] > num[j])) {
tmp[k] = num[i];
i++;
} else {//否则右边数组中的学号大,产生逆序对
tmp[k] = num[j];
j++;
//计算逆序对数量
res += m - i;
}
}
//将排好顺序的数据复制回num数组中
for (int k = l; k < r; k++)
num[k] = tmp[k];
return res;
}
int main() {
int n = 0;
cin >> n;
for (int i = 0; i < n; i++)
cin >> num[i];
cout << merge(0, n) << endl;
return 0;
}