【CSP】2021-12-2 序列查询新解 分段处理 用乘法代替加法减少时间复杂度(思想是离散化)

2021-12-2 序列查询新解 分段处理 用乘法代替加法减少时间复杂度(思想是离散化)2021-12-2 序列查询新解 分段处理 用乘法代替加法减少时间复杂度(思想是离散化)

2021-12-2 序列查询新解 分段处理 用乘法代替加法减少时间复杂度(思想是离散化)

这个题目挺有意思,比之前几年我做过的都要有些难,但是也不是难吧,就是没有见过这种的,然后就想不到怎么去优化时间复杂度。

一开始我以为题目的优化思路是预处理,就是提前算好f和g然后再减在一起算出sum,我还抱着侥幸心理,以为 1 0 9 10^9 109的复杂度不会超时,但是最后还是超时了,此时我就是黔驴技穷了,我只是想到在 1 0 9 10^9 109的基础上优化,但是没有想到怎么用直接使用 1 0 5 10^5 105分段进行计算。然后看了别人的思路才有了思路。希望考试不要出现这种情况,因为考试可没有别人的思路给你看。

思路

这题的思路不同于之前的第二题,实用什么的差分啊,什么二分搜索啊,什么动态规划啊都没有使用到。而是不同寻常的使用了分段的思想。

我一开始的想法是使用一个数组把f全部算出来,但是发现并不行会超时,并且也没有用空间来换取时间,但是其中有很多重复的加法,可以用乘法替换。

我们拿到一个问题一般是要将问题规约成更小的问题,那么如何规约就会导致问题的时间复杂度和空间复杂度不同,这题你要是想用空间换时间(就是把临时搜索的数据存储下来,后边可以不搜索直接使用的思想)不太行,而是要减少计算的次数,基本就是将加换成乘。

那么怎么将加换成乘呢?就是将相同的加合并,对应这个题目就是算f和g的时候,我们可以把一段区间内的加变成一次乘法。 那么这个区间就是当g和f 相同的区间,g的变化是有规律的,f 的变化也是有规律的,我们就是判断一个区间是不是f和g都不变化那么我们就可以直接加上区间长度和 ∣ f − g ∣ |f-g| fg​的乘积 。

解题过程

  1. 我首先就是按照我想的那种思路写了一种代码,直接就70分了,剩下全部超时了

在这里插入图片描述

  1. 然后我就想找一种优化的方法,比如使用常数级优化,O2优化等,全都是超时,可以发现只要 复杂度是 1 0 9 10^9 109​怎么优化也没有用的。

    测试只有一个for循环 1 0 9 10^9 109次什么也不干,就会超时

    在这里插入图片描述

  2. 然后看了别人的思路,说使用分段的思想,我瞬间就明白了,就写代码

在这里插入图片描述

但是发现错误不太行,原来是没有使用long long

改用了long long 后就可以了
在这里插入图片描述

超时的完整代码

#include <bits/stdc++.h>
using namespace std;
int n, N;
int a[100001];
int f[100000001];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> N;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    int top = 1;
    int r = N / (n + 1);
    long long sum = 0;
    for (int i = 0; i < N; ++i)
    {
        if (i < a[top])
        {
            f[i] = top - 1;
        }
        else
        {
            f[i] = top;
            if (top < n)
                top++;
            else
            {
                sum += abs(i / r - top);
                continue;
            }
        }
        sum += abs(i / r - f[i]);
    }
    cout << sum;
    return 0;
}

100分的完整代码

#include <bits/stdc++.h>
using namespace std;
int n, N;
int a[100001];
int main()
{
    cin >> n >> N;
    int maxa = -1;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        maxa = max(a[i], maxa);
    }
    int r = N / (n + 1);
    int f = 0;
    long long sum = 0;
    for (int i = 1; i <= n; i++)
    {
        // 判断g和f谁先变
        // f是遇到元素才变 g是隔r个数就变的
        f = i - 1;
        int length = a[i] - a[i - 1];
        int gl = a[i - 1] / r;
        int gr = (a[i] - 1) / r; // 不包括右端点,因为右端点f的值已经变化了
        if (gr == gl)            // 如果这个区间g的值没有变化
        {
            sum += abs(f - gl) * length;
        }
        else // 如果g有变化那么再分区间
        {
            int left = a[i - 1]; // 不断更新区间的左端点 知道超过f不变的区间
            while (left < a[i])
            {
                int length = min(a[i], (gl + 1) * r) - left;
                sum += abs(f - gl) * length;
                // 更新状态
                left = (gl + 1) * r;
                gl++;
            }
        }
    }
    if (maxa < N) // 遍历超出了a的数,此时f恒等于n g还是原来的变化规律
    {
        int f = n;
        int left = a[n];
        int gl = left / r;
        while (left <= N)
        {
            int length = min(N, (gl + 1) * r) - left;
            sum += abs(f - gl) * length;
            // TODO:更新状态
            left = (gl + 1) * r;
            gl++;
        }
    }
    cout << sum;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值