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

为了更好的阅读体检,可以查看我的算法学习网
在线评测链接:P1050

题面

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

输入格式

第一行两个整数 n ( 1 ≤ n ≤ 2 × 1 0 6 ) , x ( − 2 × 1 0 6 ≤ x ≤ 2 × 1 0 6 ) n(1 \leq n \leq 2 \times 10^6),x(-2 \times 10^6 \leq x \leq 2 \times 10^6) n(1n2×106),x(2×106x2×106),分别代表整数的个数和题目中的 x x x

第二行 n n n个用空格隔开的整数,第 i i i个代表 − 2 × 1 0 6 ≤ a i ≤ 2 × 1 0 6 -2 \times 10^6 \leq a_i \leq 2 \times 10^6 2×106ai2×106

输出格式

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

样例

input

5 1
1 1 5 4 2

ouput

3

提示

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

思路

1.双指针暴力法

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

n n n 带进去看看 ( 2 ∗ 1 0 6 ) 2 = 4 ∗ 1 0 12 > > 1 e 8 (2 * 10^6)^2=4 * 10^{12} >> 1e8 (2106)2=41012>>1e8

2.桶预处理法

先将所有数装进桶中。扫一遍数组枚举每一个 a i a_i ai , 那么此时已知的数(常数)为 a i , x a_i,x ai,x
① a i − a j = x ② a j = a i − x ①a_i-a_j=x \\ \\ ②a_j=a_i-x aiaj=xaj=aix
所以我们的任务就是在整个序列中寻找有多少个 a j a_j aj 满足等式②。由于我们已经预处理了桶。所以直接查询 a i − x a_i-x aix的出现次数即可。

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

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

实现

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

#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= x aiaj=x 改成 a i − a j = i − j a_i-a_j= i-j aiaj=ij

​ 2.序列中有多少个子段的和恰好为 x x x,即求有多少有序对( i , j i,j i,j) i < j i < j i<j满足
∑ k = i j a k = x \sum_{k=i}^{j}a_k=x k=ijak=x
​ 3.序列中有多少个子段的和为 x x x的倍数,即求有多少有序对 ( i , j (i,j (i,j)满足
∑ k = i j a k ≡ 0   ( m o d   x ) \sum_{k=i}^{j} a_k \equiv 0 \ (mod\ x) k=ijak0 (mod x)
​ 4.序列中有多少个子段的平均值恰好为 x x x,即求有多少有序对 ( i , j (i,j (i,j)满足
∑ k = i j a k j − i + 1 = x \frac{\sum_{k=i}^{j} a_k}{j-i+1}=x ji+1k=ijak=x
题目内容均收集自互联网,如如若此项内容侵犯了原著者的合法权益,可联系我: (CSDN网站注册用户名: 塔子哥学算法) 进行删除。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值