前缀和
适用于静态数组区间和
时间复杂度:O(n)
原理:当两个整数a,b对k具体相同余数(a%k==b%k),那么a - b一定为k的倍数。( a != b )
一维前缀和
题目一:k倍区间
给定一个长度为N的数列,A1, A2, ... AN,
如果其中一段连续的子序列Ai, Ai+1, ... Aj(i <= j)之和是K的倍数,
我们就称这个区间[i, j]是K倍区间。
你能求出数列中总共有多少个K倍区间吗?
输入
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100000)
输出
输出一个整数,代表K倍区间的数目。
例如,
输入:
5 2
1
2
3
4
5
程序应该输出:
6
简要分析:
当前缀和sum1%k=3、sum2%k=3(sum1>sum2)
它们重合的区间和:sum1-sum2%k=0,为k的倍数。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll N, K, ans=0;
ll sum[100010];
ll cnt[100010];
int main()
{
cin>>N>>K;
memset(cnt, 0, sizeof(cnt));
sum[0]=0;
for(int i=1; i<=N; i++)
{
int a;
cin>>a;
//前缀和
sum[i] = (sum[i-1] + a)%K;
cnt[sum[i]]++;
}
//情况一
ans += (cnt[0]*(cnt[0]+1))/2;
//情况二
for(int i=1; i<K; i++)
ans += (cnt[i]*(cnt[i]-1))/2;
cout<<ans;
return 0;
}
题目二:7倍最长区间
给你n个数,分别是a[1],a[2],...,a[n]。求一个最长的区间[x,y],
使得区间中的数(a[x],a[x+1],a[x+2],...,a[y-1],a[y])的和能被7整除。
输出区间长度。若没有符合要求的区间,输出0。
题目二与题目一大致方法相同,只是求的答案不同,灵活变化即可。
#include<bits/stdc++.h>
using namespace std;
long long sum[100000];
int main()
{
int n;
cin>>n;
memset(sum, 0, sizeof(sum));
for(int i=1; i<=n; i++)
{
int a;
cin>>a;
sum[i] = (sum[i-1]+a)%7;
}
int begin[7], end[7];
memset(begin, 0, sizeof(begin));
memset(end, 0, sizeof(end));
for(int i=n; i>0; i--)
begin[sum[i]]=i;
for(int i=1; i<=n; i++)
end[sum[i]]=i;
int len=0;
for(int i=0; i<7; i++)
len = max(len, end[i]-begin[i]);
cout<<len;
return 0;
}
二维前缀和:寻找最大正方形
在一个n*m的只包含0和1的矩阵里找出一个**不包含0**的最大正方形,
输出边长。
#include<bits/stdc++.h>
using namespace std;
int s[110][110];
int main()
{
int n,m;
cin>>n>>m;
memset(s, 0, sizeof(s));
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
cin>>s[i][j];
s[i][j] = s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
}
int l = 2;
int ans = 1;
//暴力枚举不同大小的正方形
while(l <= min(n, m))
{
//暴力枚举每个点作为起始点
for(int i=l; i<=n; i++)
for(int j=l; j<=m; j++)
{
if(s[i][j]-s[i][j-l]-s[i-l][j]+s[i-l][j-l] == l*l)
ans = max(ans, l);
}
l++;
}
cout<<ans<<endl;
return 0;
}
差分
适用于多次给不同的区间赋予增量。
时间复杂度:O(n)求数组最终值
一维差分
简要分析:相比于暴力方式将要改变的连续区间上的数据一个个处理,差分实现了连续区间快速变化的效果。
差分仅更改变化区间两侧的数值,再通过一次递推得出全部区间最终的数值大小。
题目一:涂气球
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
while(1)
{
int qq[100010];
memset(qq, 0, sizeof(qq));
cin>>n;
if(n==0) return 0;
for(int i=1; i<=n; i++)
{
int a, b;
cin>>a>>b;
qq[a]++;
qq[b+1]--;
}
for(int i=1; i<=n; i++)
{
qq[i]=qq[i]+qq[i-1];
cout<<qq[i]<<" ";
}
cout<<endl;
}
return 0;
}
题目二:排列计算
一个选手给出一个长度为 n 的排列,另一个选手给出 m 个询问,
每次询问是一个形如 (l, r) 的数对,
查询队友给出的排列中第 l 个数到第 r 个数的和,
并将查询到的这个区间和加入总分,
最后总分最高的队伍就能获胜。
聪明的你能不能预测出队伍最终的得分呢?
一个排列是一个长度为 n 的数列,
其中 1 ~ n 中的每个数都在数列中恰好出现一次。
比如 [1, 3, 2] 是一个排列,而 [2, 1, 4] 和 [1, 2, 3, 3] 不是排列。
输入描述:
第一行输入两个数 n (1≤n≤2×10^5^) 和 m (1≤m≤2×10^5^) 。
接下来 m 行,每行输入两个数 l 和 r ,
代表这次查询排列中第 l 个到第 r 个的和。
输出描述:
输出一个整数,代表他们队伍总分的最大值。
示例1
输入
7 3
1 3
3 7
5 6
输出
46
说明
一个符合条件的排列是 [1,3, 6, 4, 7, 5, 2],
于是最终的得分为 (1 + 3 + 6) + (6 + 4 + 7 + 5 + 2) + (7 + 5) = 46
简要解析:统计每个位置出现的次数,将出现次数越多的位置记为越大的数,最终结果就是最大值。
#include<bits/stdc++.h>
using namespace std;
int count_sz[200010];
long long ans=0;
int main()
{
int n, m;
cin>>n>>m;
memset(count_sz, 0, sizeof(count_sz));
for(int i=0; i<m; i++)
{
int l, r;
cin>>l>>r;
count_sz[l]++;
count_sz[r+1]--;
}
for(int i=1; i<=n; i++)
count_sz[i]=count_sz[i-1]+count_sz[i];
sort(count_sz+1, count_sz+n+1);
for(int i=1; i<=n; i++)
ans += count_sz[i]*i;
cout<<ans;
return 0;
}
二维差分
举例分析:
①、创建差分数组
将大小为sum[5][4]二维数组的左下角为[1,1]右上角为[3,3]构成的区间增加10,创建上图差分二维数组
②、赋值后影响区间
绿色区间:[1,1]影响的区间
蓝色区间:[1,1]与[1,4]共同影响的区间
黄色区间:[1,1]与[4,1]共同影响的区间
棕色区间:[1,1]、[4,1]、[1,4]共同影响的区间
③、递推式
s[i][j]=s[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1]
通过上面的递推式,递推出最终的差分数组。
将最终得到的差分数组上的元素分别与二维数组中下标对应的元素相加,得到最终变化后的二维数组。
题目:铺地毯
#include<bits/stdc++.h>
using namespace std;
int dt[1010][1010];
int main()
{
int n, m;
memset(dt, 0, sizeof(dt));
cin>>n>>m;
for(int i=0; i<m; i++)
{
int x1, y1, x2, y2;
cin>>x1>>y1>>x2>>y2;
dt[x1][y1]++;
dt[x2+1][y2+1]++;
dt[x2+1][y1]--;
dt[x1][y2+1]--;
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
dt[i][j] = dt[i][j]+dt[i][j-1]+dt[i-1][j]-dt[i-1][j-1];
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
cout<<dt[i][j]<<" ";
cout<<endl;
}
return 0;
}
题目参考文章
希望将自己的学习经验分享给有需要的人。
我是小郑,一个坚持不懈的小白