2021-12-2 序列查询新解 分段处理 用乘法代替加法减少时间复杂度(思想是离散化)2021-12-2 序列查询新解 分段处理 用乘法代替加法减少时间复杂度(思想是离散化)
- 2021-12-2 序列查询新解 分段处理 用乘法代替加法减少时间复杂度(思想是离散化)
- 思路
- 解题过程
- 超时的完整代码
- 100分的完整代码
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| ∣f−g∣的乘积 。
解题过程
- 我首先就是按照我想的那种思路写了一种代码,直接就70分了,剩下全部超时了
-
然后我就想找一种优化的方法,比如使用常数级优化,O2优化等,全都是超时,可以发现只要 复杂度是 1 0 9 10^9 109怎么优化也没有用的。
测试只有一个for循环 1 0 9 10^9 109次什么也不干,就会超时
-
然后看了别人的思路,说使用分段的思想,我瞬间就明白了,就写代码
但是发现错误不太行,原来是没有使用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;
}