Codeforces:All Pairs Segments

前言

        这是一道1200分cf题,做了三天,思维题还是不太行,最后还是参考了一个大佬的思路才AC。(我想吐槽一下,这个分数是不是标错了啊,难度远比1200高,呜呜呜~)

一、题目

        题目链接:Codeforces All Pairs Segments

二、我的心路历程

(一)纯暴力

        由于蒟蒻现在还不会算时间复杂度,所以老毛病又犯了,上来就直接暴力,暴力到一半突然注意到题目的标签是模拟,就知道暴力肯定会TL了。

(二)尝试优化:模拟+小暴力

        既然题目的标签是模拟,那我们就模拟一下。模拟之后可以发现,对于在区间端点的数包含在i(n−i+1)−1个区间中,而在区间内的数包含在i(n−i)个区间中。(n为第二行输入整数的个数,i为某个数在这n个数中的索引),再搭配键值对map,于是便有了下列代码:

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);

    int t;
    cin >> t;

    while (t--) {
        int n, q;
        cin >> n >> q;

        int point[n + 1];
        for (int i = 1; i < n + 1; ++i) {
            cin >> point[i];
        }

        map<long long, long long> count;
        int j = 1;
        for (int i = point[1]; i <= point[n]; ++i) {
            if (i == point[j]) {
                long long a = j * (n - j + 1) - 1;
                count[a]++;
                j++;
            } else {
                long long a = (j - 1) * (n - j + 1);
                count[a]++;
            }
        }

        for (int i = 0; i < q; ++i) {
            long long k;
            cin >> k;
            cout << count[k] << " ";
        }
        cout << endl;
    }

    return 0;
}

虽然用了模拟之后的公式,大大减少了运行时间,但仍然TL,第一个样例都没过。
TL

(三)再次尝试优化:用哈希表unordered_map替换map

        map是基于红黑树实现的关联容器,它的元素是按照键值对进行排序存储的,插入、查找和删除操作的平均时间复杂度都是 O(logn),其中 n 是容器中的元素数量。但在本题场景下,如果不需要元素按照键值排序,使用 map 可能会带来一些不必要的性能开销,因为每次插入或查找操作都需要维护树的结构以保证排序特性。
        但unordered_map是基于哈希表实现的关联容器,在理想情况下(哈希函数分布均匀,没有大量哈希冲突),插入、查找和删除操作的平均时间复杂度可以接近O(1),相比 map 在平均性能上有较大提升。虽然在最坏情况下,当出现大量哈希冲突时,其时间复杂度可能会退化为O(n),但只要合理设计哈希函数以及在实际应用场景下,一般可以保证较好的性能。
        在本题这种主要是进行计数信息存储和查询的场景下,unordered_map 通常会比 map 更高效。所以我选择了用哈希表unordered_map替换map再次进行优化。即变化如下:

map<long long, long long> count;
           //变为
unordered_map<long long, long long> count;

但意料之中,还是TL。

(四)再再次尝试优化:用变量packup备份(n-j+1)运算结果,避免重复计算

        加减乘除运算时间极短,在运算次数较少的时候可以忽略,但这题数据范围之大,运算次数极多,不可忽略。
        所以我对重复出现的(n-j+1)用packup进行备份,可以减少一些时间复杂度,如下:

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);

    int t;
    cin >> t;

    while (t--) {
        int n, q;
        cin >> n >> q;

        int point[n + 1];
        for (int i = 1; i < n + 1; ++i) {
            cin >> point[i];
        }

        unordered_map<long long, long long> count;
        int j = 1, backup = n;

        for (int i = point[1]; i <= point[n]; ++i) {
            if (i == point[j]) {
                long long a = j * backup - 1;
                count[a]++;
                j++;
                backup--;
            } else {
                long long a = (j - 1) * backup;
                count[a]++;
            }
        }

        for (int i = 0; i < q; ++i) {
            long long k;
            cin >> k;
            cout << count[k] << " ";
        }

        cout << endl;
    }

    return 0;
}

没错,又是TL,到这里我都快要疯了。

(五)再再再次尝试优化:关闭输入输出流同步

        因为相比于 C 语言中的printf和scanf,cin和cout的缓冲管理相对复杂;而printf和scanf的缓冲策略相对简单直接,而cin和cout作为面向对象的流对象,其缓冲的维护涉及到更多的对象状态管理。但影响不大,因为我用ios::sync_with_stdio(false),cout.tie(0),cin.tie(0);关闭了iostream和stdio之间的同步机制,样做可以显著提高输入输出的效率;又因为输入输出的次数很少,所以不会有太大的影响。
        没错,又又又又TL了,我人麻了。

(六)再再再再次尝试优化:将cin改为scanf,cout改为printf

        直接更换比关闭输入输出更快,但还是没用。

(七)再再再再再次优化:#define int long long和signed main()

        先看下面的题解,我在用了题解的思路之后,进行了提交,但WA了,如下图:WA
Probably, the solution is executed with error 'signed integer overflow' on the line 19这句话的意思是可能,该解决方案在第 19 行执行时出现了 “有符号整数溢出” 的错误。于是我又加上了#define int long long,将溢出int数的数据类型改为long long
        又因为主函数不能用long long main(),所以又改为signed main(),见下AC码,提交之后,成功AC
        这里注意:该long long的都long long了啊,这里为什么会溢出呢?因为count是<long long, long long>,而count[(i-1)*(n-i)+i-1+n-i]中的[(i-1)*(n-i)+i-1+n-i],其中的变量i和n都是int类型的,所以整个[(i-1)*(n-i)+i-1+n-i]也是int类型的,当然会溢出,只要n和i其中一个是long long类型,整个[(i-1)*(n-i)+i-1+n-i]就会变成long long类型。所以直接#define int long long就AC了,或者将其中一个变量改为long long类型,再或者直接在运算时(long long)其中一个变量,再或者LL其中一个常数。

三、题解

        我写的代码,不管再怎样优化,整体框架大致没有变,都是将所有数从头到尾全部遍历一遍。其实应该换一个思路通过遍历第二行输入的n个数,间接遍历所有区间,这样时间复杂度只有nlog(n)。
AC码:

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long

signed main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);

    int t;
    cin >> t;

    while (t--) {
        int n, q;
        cin >> n >> q;

        int point[n + 1];
        for (int i = 1; i < n + 1; ++i) {
            cin >> point[i];
        }

        unordered_map<long long, long long> count;

        for (int i = 1; i <= n; ++i) {
            count[i * (n - i + 1) - 1]++;
        }

        for (int i = 2; i <= n; ++i) {
            count[(i - 1) * (n - i + 1)] += (point[i] - point[i - 1] - 1);
        }

        for (int i = 0; i < q; ++i) {
            long long k;
            cin >> k;
            cout << count[k] << " ";
        }

        cout << endl;
    }

    return 0;
}

// ⠀⠀⠀             ⠀⢸⣿⣿⣿⠀⣼⣿⣿⣦⡀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀ ⠀⢸⣿⣿⡟⢰⣿⣿⣿⠟⠁
// ⠀⠀⠀⠀⠀⠀⠀⢰⣿⠿⢿⣦⣀⠀⠘⠛⠛⠃⠸⠿⠟⣫⣴⣶⣾⡆
// ⠀⠀⠀⠀⠀⠀⠀⠸⣿⡀⠀⠉⢿⣦⡀⠀⠀⠀⠀⠀⠀ ⠛⠿⠿⣿⠃
// ⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⠀⠀⠹⣿⣶⡾⠛⠛⢷⣦⣄⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣧⠀⠀⠈⠉⣀⡀⠀ ⠀⠙⢿⡇
// ⠀⠀⠀⠀⠀⠀⢀⣠⣴⡿⠟⠋⠀⠀⢠⣾⠟⠃⠀⠀⠀⢸⣿⡆
// ⠀⠀⠀⢀⣠⣶⡿⠛⠉⠀⠀⠀⠀⠀⣾⡇⠀⠀⠀⠀⠀⢸⣿⠇
// ⢀⣠⣾⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⢀⣼⣧⣀⠀⠀⠀⢀⣼⠇
// ⠈⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⠋⠙⠛⠛⠛⠛⠛⠁
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⡿⠋⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⢾⠿⠋

四、总结

        这道题还是挺有难度的,考的就是模拟,模拟之后还要想该怎样去优化,到最后还是把整个思路换了才AC,将遍历数换成遍历两数之间的区间对我这种蒟蒻还是挺有难度的。另外还收获到了一个小技巧:#define int long long,学了一招,以后不管写什么程序都要加上这个,但别忘了将int main()改成signed main()。还有,一定一定一定 要注意 (七) 中最后一段的描述,这是 超级超级超级 易错的点!!!

结语

我只是一个A C M预习时长两年半的蒟蒻练习生,喜欢暴力,打表,面向样例编程,请各位键盘侠手下留情。
两年半

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-George-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值