前缀和的应用

题目1:k倍区间

给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

输入格式
第一行包含两个整数 N 和 K。

以下 N 行每行包含一个整数 Ai。

输出格式
输出一个整数,代表 K 倍区间的数目。

数据范围
1≤N,K≤100000,
1≤Ai≤100000
输入样例:
5 2
1
2
3
4
5
输出样例:
6

解题思路:
题目要求求所给区间的子区间和,可以暴力枚举所有子区间,然后利用前缀和求区间和。枚举子区间一般有两种方式:枚举区间的两端点,或者枚举区间的某端点和区间的长度。这两种方式都需要两重循环,时间复杂度为O(n2)根据题目所给数据范围就超出了。可以在此基础上对代码进行优化。
其中两重循环的代码为

for(int r = 1; r <= n; i++)
	for(int l = 1; l <= r; l++)
		if((s[r] - s[l-1]) % k == 0) res ++;

可以发现,我们在内层循环中实际上找的是在0~r-1中有多少个s[i]与s[r]的余数相同,这样就又可以用桶进行优化。

优化后的代码为:

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;
typedef long long LL;

int n, k;
LL s[N];
int cnt[N];

int main()
{
	cin >> n >> k;
	for(int i = 1; i <= n; i++)
	{
		scanf("%lld", &s[i]);
		s[i] += s[i-1];
	}
		
	LL res = 0;
	for(int i = 0; i <= n; i++)
	{
		res += cnt[s[i] % k];
		cnt[s[i] % k] ++;
	}

	cout << res << endl;
	return 0;
}

桶数组的妙用
问题:给定一个n个数的序列和一个整数k,求在第一个数到第i-1个中有多少个数与第i个数除以k的余数相同。

利用桶数组可以把时间复杂度降到O(n)

for(int i = 0; i <= n; i++)
{
	res += cnt[a[i] % k];	//cnt[x]表示在a[0]到a[i-1]中余数为x的数有多少个
	cnt[a[i] % k] ++;
}

题目2:递增三元组
给定三个整数数组

A=[A1,A2,…AN],
B=[B1,B2,…BN],
C=[C1,C2,…CN],

请你统计有多少个三元组 (i,j,k) 满足:

1≤i,j,k≤N
Ai<Bj<Ck
输入格式
第一行包含一个整数 N。

第二行包含 N 个整数 A1,A2,…AN。

第三行包含 N 个整数 B1,B2,…BN。

第四行包含 N 个整数 C1,C2,…CN。

输出格式
一个整数表示答案。

数据范围
1≤N≤105,
0≤Ai,Bi,Ci≤105
输入样例:
3
1 1 1
2 2 2
3 3 3
输出样例:
27

解题思路:
首先会想到暴力枚举,三重循环枚举所有情况,发现数据范围太大会超时
想优化,根据数据范围可以接受的时间复杂度为O(n)、O(nlogn)也就是只能枚举一个数组,由于A, B, C要求有序,所以枚举B数组,然后对于枚举每一个B看有多少个符合要求的A, C最终结果为A*C。
求一个数组中大于某个数的元素个数:前缀和、sort+二分、双指针

前缀和做法:
用桶数组记录每个元素出现的次数,然后对求桶数组的前缀和s[i]就表示数组中0~i的元素出现的次数。

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 1e5 + 10;

int n;
int as[N], cs[N];
int a[N], b[N], c[N];

int main()
{
    cin >> n;
    for(int i = 0; i < n; i++) scanf("%d", &a[i]), a[i]++;
    for(int i = 0; i < n; i++) scanf("%d", &b[i]), b[i]++;
    for(int i = 0; i < n; i++) scanf("%d", &c[i]), c[i]++;
    
    for(int i = 0; i < n; i++) as[a[i]]++;
    for(int i = 0; i < n; i++) cs[c[i]]++;
    
    for(int i = 1; i < N; i++)
    {
        as[i] += as[i-1];
        cs[i] += cs[i-1];
    }
    
    long long res = 0;
    for(int i = 0; i < n; i++)
    {
        long long sum1, sum2;
        sum1 = as[b[i]-1];
        sum2 = cs[N-1] - cs[b[i]];
        res += sum1 * sum2;
    }
    
    cout << res << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值