为了更好的阅读体检,可以查看我的算法学习博客差分计数
题目内容
给定n个整数,...,和一个整数x。求有多少有序对(i,j)满足
输入格式
第一行两个整数,分别代表整数的个数和题目中的x。
第二行n个用空格隔开的整数,第i个代表
输出格式
一行一个整数,代表满足的有序对(i,j)个数。
样例
input
5 1
1 1 5 4 2
ouput
3
提示
(i,j) 为(5,1),(5,2),(3,4)
思路
1.双指针暴力法
双重循环枚举i,j 来计数即可,复杂度是。但是无法拿到满分。 服务器一般一秒跑1e8次。
把n带进去看看
2.桶预处理法
先将所有数装进桶中。扫一遍数组枚举每一个, 那么此时已知的数(常数)为,x
①
②=
所以我们的任务就是在整个序列中寻找有多少个a_j满足等式②。由于我们已经预处理了桶。所以直接查询$a_i-x$的出现次数即可。
有一个特殊情况:当x=0 时i = j也可以。但是题目貌似没规定是否可以相等。如果可以相等就不用改,如果不能相等就得特判 - 1
此时复杂度显然就是O(n)的了
实现
注意,在实现的过程中还是需要注意很多细节问题。
c++
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 5;
int a[maxn];
int b[maxn * 2]; // 注意:b的下标填的是值域范围,不是数组长度,所以需要开两倍
// 下标变换
int idTrans (int x){
// 这时下标从[-2e6,2e6]映射到[0,4e6]
return x + 2e6;
}
int main() {
int n , x;
cin >> n >> x;
for (int i = 1 ; i <= n ; i++)
cin >> a[i];
// 第一步:将数装进桶中
// 可以用STL中的unordered_map,但是我们发现下标其实没那么大
// 所以为了更快的运行速度我们可以开一个桶数组b。(题目只给了0.5s)
// 但是值域涉及到负数,所以需要做一个下标的映射.
for (int i = 1 ; i <= n ; i++){
b[idTrans(a[i])]++;
}
// 第二步:枚举每个数,统计答案
// 答案可能很大:考虑ai全等且x=0,那么任意两个i,j都是一个答案。那么答案会是n^2阶的。
// 尝试将其带进去会发现它爆int了,所以只能用long long 存储
long long ans = 0;
for (int i = 1 ; i <= n ; i++)
ans += b[idTrans(a[i] - x)];
cout << ans << endl;
return 0;
}
总结
评价
本题正式涉及到算法思想,鉴定为竞赛入门难度。
关键
这道题的优化关键在于桶预处理
拓展
1.将题目中的a_i-a_j= x 改成 a_i-a_j= i-j
2.序列中有多少个子段的和恰好为$x$,即求有多少有序对(i,j)i < j满足
3.序列中有多少个子段的和为$x$的倍数,即求有多少有序对(i,j)满足
4.序列中有多少个子段的平均值恰好为$x$,即求有多少有序对(i,j)满足