合适数对

合适数对

给定一个长度为 n 的整数数列 a1,a2,,an 和一个整数 t

请你判断共有多少个数对 (l,r) 同时满足:

  • 1lrn
  • al+al+1++ar1+ar<t

输入格式

第一行包含两个整数 nt

第二行包含 n 个整数 a1,a2,,an

输出格式

一个整数,表示满足条件的数对的数量。

数据范围

前三个测试点满足 1n5
所有测试点满足 1n2×105|t|2×1014|ai|109

输入样例1:

5 4
5 -1 3 4 -1

输出样例1:

5

输入样例2:

3 0
-1 2 -3

输出样例2:

4

输入样例3:

4 -1
-2 1 -2 3

输出样例3:

3

 

解题思路

  设前缀和si表示a1++ai,我们枚举右端点i和左端点j,来找到有多少个区间满足sisj1<t。也就是i确定后,求前面有多少个sj1满足这个不等式。

  由于我们是枚举i,当枚举到i的时候si已经是一个定值了,而sj1是要求的变量,因此sj1应该满足sj1>sit。这就相当于问我们在i的前面有多少个前缀和满足大于sit的。其中j的取值范围为1ji,因此有0j1i1,也就是问在0ji1这个区间内,有多少个sj满足sj>sit

  因此每次枚举,我们要维护前面i个数一个有序序列,枚举完i后,把si放入这个有序序列。下次要找有多少个大于sit的数时,就可以通过二分找到大于sit的第一个数,那么之后的数都是满足大于sit的,可以直接求得有多少个数满足这条件。

  其中在这个题中ai是可以小于0的,如果ai都满足ai0,那么可以用双指针做。由于ai0,因此前缀和数组是单调递增的,根据sj1>sit,可以发现当i增大往后移动,sit会变大,因此sj1也应该变大,即j也要往后移动。因此两个指针的移动都是单调的。

  满足这个条件的代码如下:

复制代码
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

LL s[N];

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int val;
        scanf("%d", &val);
        s[i] = s[i - 1] + val;
    }
    
    LL ret = 0;
    for (int i = 1, j = 0; i <= n; i++) {
        while (s[j] <= s[i] - m && j < i) {
            j++;
        }
        ret += i - j;
    }
    
    printf("%lld", ret);
    
    return 0;
}
View Code
复制代码

  对于这道题目,由于ai是可以小于0的,因此前缀和数组不满足单调性,就不可以用上面的方法了。因为维护有序序列,因此可以用平衡树,但目前我只学过用std::set实现的平衡树,这个可以维护有序序列,但不能够知道某个数在这个有序序列的具体下标,这样就不能够知道这个数后面有几个数了。

  这里用另外一种方法,树状数组加离散化。

  树状数组的作用是,当枚举到i,我们通过树状数组来求大于sit的数的个数,具体方法就是先通过树状数组来求得有多少个数sit,然后用i减去这个值就得到大于sit的数的个数了。然后枚举完i后,si这个数出现次数加1,通过树状数组来维护动态前缀和。

  要离散化是因为ai的取值非常大,因此我们需要进行离散化。我们用到的数有每一个si以及sit。还有0,因为一开始枚举i=1的时候j1的取值为00,因此会用到s0,即0。通过离散化后,最终需要用到的值的数量不超过2×N

  AC代码如下:

复制代码
 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 typedef long long LL;
 6 
 7 const int N = 4e5 + 10;
 8 
 9 LL s[N], xs[N], cnt;
10 int tr[N];
11 
12 int find(LL x) {
13     int left = 1, right = cnt;
14     while (left < right) {
15         int mid = left + right >> 1;
16         if (xs[mid] >= x) right = mid;
17         else left = mid + 1;
18     }
19     
20     return left;
21 }
22 
23 int lowbit(int x) {
24     return x & -x;
25 }
26 
27 void add(int x, int val) {
28     for (int i = x; i <= cnt; i += lowbit(i)) {
29         tr[i] += val;
30     }
31 }
32 
33 int query(int x) {
34     int ret = 0;
35     for (int i = x; i; i -= lowbit(i)) {
36         ret += tr[i];
37     }
38     
39     return ret;
40 }
41 
42 int main() {
43     int n;
44     LL m;
45     scanf("%d %lld", &n, &m);
46     for (int i = 1; i <= n; i++) {
47         int val;
48         scanf("%d", &val);
49         s[i] = s[i - 1] + val;
50         xs[++cnt] = s[i], xs[++cnt] = s[i] - m; // 用到s[i]和s[i]-m
51     }
52     
53     xs[++cnt] = 0;  // 0也会被用到,因此需要加到离散化数组中
54     sort(xs + 1, xs + cnt + 1);
55     cnt = unique(xs + 1, xs + cnt + 1) - xs - 1;
56     
57     LL ret = 0;
58     add(find(0), 1);    // 一开始有0
59     for (int i = 1; i <= n; i++) {
60         ret += i - query(find(s[i] - m));   // 大于s[i]-m的数就是已有的数减去小于等于s[i]-m的数的个数
61         add(find(s[i]), 1); // s[i]出现过,因此s[i]出现次数加1
62     }
63     
64     printf("%lld", ret);
65     
66     return 0;
67 }
复制代码

 

参考资料

  AcWing 4316. 合适数对(AcWing杯 - 周赛):https://www.acwing.com/video/3739/

posted @   onlyblues  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示