给定一个数组 A 和一些查询 Li,Ri,求数组中第 Li 至第 Ri 个元素之和。
小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。
小蓝想知道相比原数组,所有查询结果的总和最多可以增加多少?
输入格式
输入第一行包含一个整数 n。
第二行包含 n 个整数 A1,A2,⋅⋅⋅,An,相邻两个整数之间用一个空格分隔。
第三行包含一个整数 m 表示查询的数目。
接下来 m 行,每行包含两个整数 Li、Ri,相邻两个整数之间用一个空格分隔。
输出格式
输出一行包含一个整数表示答案。
数据范围
对于 30% 的评测用例,n,m≤50;
对于 50% 的评测用例,n,m≤500;
对于 70% 的评测用例,n,m≤5000;
对于所有评测用例,1≤n,m≤105,1≤Ai≤106,1≤Li≤Ri≤n。
输入样例:
5
1 2 3 4 5
2
1 3
2 5
输出样例:
4
样例解释
原来的和为 6+14=20,重新排列为 (1,4,5,2,3) 后和为 10+14=24,增加了 4。
代码
// 给出的样例看作可以先找出 1 ~ 3 和 2 ~ 5的交集,
//然后让 4 5 两个较大的数放在了 2 3 的位置上
//所以解题思路可以先猜想为:找出区间之间的交集,并记录重复次数,重复次数最多的位置放最大的值
// 那么我们该怎么统计这些交集?大家可以看下代码以及后面的总结
#include
#include
#include
using namespace std;
const int N = 1e5 + 10;
// long long 不用解释了吧,这题不很明显 可能会爆 int 嘛。
long long arr[N];
long long cnt[N]; // 一维差分,恢复后就是每个位置 出现的次数。
int main()
{
int n; cin >> n;
for(int i = 1; i <= n; ++i){
scanf("%d", &arr[i]);
}
int m; cin >> m;
while (m -- ){
int l, r;
scanf("%d%d", &l, &r);
cnt[l]++;
cnt[r + 1]--;
}
for(int i = 1; i <= n; ++i){
cnt[i] += cnt[i - 1]; // 恢复成 统计次数的数组
}
// 后来看了其它人的题解,又发现 总和
// 其实就是把所有询问的区间内出现过的
// 【数字 * 出现次数】 相加。。。(确实是等价的,你可以自己证明!还挺好证的。。)
long long sumA = 0;
for(int i = 1; i <= n; ++i){
sumA += cnt[i] * arr[i]; // 这样确实也是总和。。我太笨了,居然没发现。
}
// 欸,可别小看了上述这个发现。它有大用。
// 既然我们说 最后的和,可以用 所有提的范围内出现过的 数字 * 出现次数 相加!
// 那么 你重新排序后,是不是 也仍然成立?当然成立!
// 只不过,此时变成了 【较大的数 * 较大的次数】
// 欸?这不就符合题意了嘛??题意就是让总和 尽可量的大呀。
/* 重新认识下,开头说的 一一对应:看懂了吗?
为什么说 不需要费劲 再去建立数组了?
因为较大的值 4 5 已经与 较大的次数 2 2 正好对应了。
所以两个 数组全部排序,再遍历一遍,进行求和就完事了啊~ 真就是太妙了,跪拜大佬 (●ˇ∀ˇ●)
arr[]: 1 2 3 4 5
cnt[]: 1 1 1 2 2
*/
long long sumB = 0;
sort(arr + 1, arr + n + 1); sort(cnt + 1, cnt + n + 1);
for(int i = 1; i <= n; ++i){
sumB += cnt[i] * arr[i];
}
cout << sumB - sumA << endl;
return 0;
}
b[l] += c;
b[r + 1] -= c;
l和r分别为要记录的数组的区间的左右界限
给定一个数组 A 和一些查询 Li,Ri,求数组中第 Li 至第 Ri 个元素之和。
输入第一行包含一个整数 n。
第二行包含 n 个整数 A1,A2,⋅⋅⋅,An,相邻两个整数之间用一个空格分隔。
第三行包含一个整数 m 表示查询的数目。
接下来 m 行,每行包含两个整数 Li、Ri,相邻两个整数之间用一个空格分隔。
5
1 2 3 4 5
2
1 3
2 5
如何计算出每个位置在所有询问区间中出现的次数呢?其实可以每次询问的时候给相应的位置 pos + 1 即可。但是这样是暴力遍历,可能得 O(n^2),太慢了。这样我们可以采用一维差分法,
#include
#include
#include
using namespace std;
int main() {
int m=2,n=5;
int cnt[5]={0}; //初始化计数数组
int arr[5] = {1, 2, 3, 4, 5};
while (m -- ){
int l, r;
scanf("%d%d", &l, &r);
cnt[l]++;
cnt[r + 1]--;
}
for(int i = 1; i <= n; ++i){
cnt[i] += cnt[i - 1]; // 恢复成 统计次数的数组
}
for(int i = 0; i < n; ++i){
cout<<cnt[i]; // 恢复成 统计次数的数组
}
return 0;
}
通过这种方法,不仅能计数,而且在很多对于数组的多个不同区间的处理的问题上都可以应用。
例子