CCF-GESP计算机学会等级考试2023年9月六级C++T2小杨的握手问题

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
4
2 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
6
0 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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长春高老师编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值