【备战秋招】每日一题:华东师范大学保研机试-2022-差分计数

为了更好的阅读体检,可以查看我的算法学习博客差分计数

题目内容

给定n个整数a_1,...,a_n和一个整数x。求有多少有序对(i,j)满足a_i-a_j= x

输入格式

第一行两个整数n(1 \leq n \leq 2 \times 10^6),x(-2 \times 10^6 \leq x \leq 2 \times 10^6),分别代表整数的个数和题目中的x。

第二行n个用空格隔开的整数,第i个代表-2 \times 10^6 \leq a_i \leq 2 \times 10^6

输出格式

一行一个整数,代表满足a_i-a_j= x的有序对(i,j)个数。

样例

input

5 1
1 1 5 4 2

ouput

3

 提示

(i,j) 为(5,1),(5,2),(3,4)

思路

1.双指针暴力法

双重循环枚举i,j 来计数即可,复杂度是O(n^2)。但是无法拿到满分。 服务器一般一秒跑1e8次。

把n带进去看看(2 * 10^6)^2=4 * 10^{12} >> 1e8

2.桶预处理法

先将所有数装进桶中。扫一遍数组枚举每一个a_i, 那么此时已知a_i的数(常数)为a_i,x

a_i-a_j=x


a_j=a_i-x

所以我们的任务就是在整个序列中寻找有多少个a_j满足等式②。由于我们已经预处理了桶。所以直接查询$a_i-x$的出现次数即可。

有一个特殊情况:当x=0 时i = j也可以。但是题目貌似没规定是否可以相等。如果可以相等就不用改,如果不能相等就得特判 - 1

此时复杂度显然就是O(n)的了

实现

​       注意,在实现的过程中还是需要注意很多细节问题。

c++
#include
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满足
\sum_{k=i}^{j}a_k=x
​       3.序列中有多少个子段的和为$x$的倍数,即求有多少有序对(i,j)满足
\sum_{k=i}^{j} a_k \equiv 0 \ (mod\ x)
​       4.序列中有多少个子段的平均值恰好为$x$,即求有多少有序对(i,j)满足
\frac{\sum_{k=i}^{j} a_k}{j-i+1}=x

你可能感兴趣的:(c++,算法,数学建模)