前缀和 & 差分ヽ(°◇° )ノ
一、前言
前缀和是一种非常重要的预处理,能大大降低查询的复杂度,在我们需要查询区间和的时候非常好用,差分是一种和前缀和相对的策略。
二、go go go
1、[USACO16JAN]Subsequences Summing to Sevens S
题目描述
Farmer John’s N cows are standing in a row, as they have a tendency to do from time to time. Each cow is labeled with a distinct integer ID number so FJ can tell them apart. FJ would like to take a photo of a contiguous group of cows but, due to a traumatic childhood incident involving the numbers 1…6, he only wants to take a picture of a group of cows if their IDs add up to a multiple of 7.
Please help FJ determine the size of the largest group he can photograph.
给你n个数,分别是a[1],a[2],…,a[n]。求一个最长的区间[x,y],使得区间中的数(a[x],a[x+1],a[x+2],…,a[y-1],a[y])的和能被7整除。输出区间长度。若没有符合要求的区间,输出0。
输入格式
The first line of input contains N (1≤N≤50,000). The next N
lines each contain the N integer IDs of the cows (all are in the range
0…1,000,000).
输出格式
Please output the number of cows in the largest consecutive group whose IDs sum
to a multiple of 7. If no such group exists, output 0.
输入输出样例
输入 #1
7
3
5
1
6
2
14
10
输出 #1
5
说明/提示
In this example, 5+1+6+2+14 = 28.
这道题要进行前缀和对7取模的预处理,这样后面我们直接输出不同余数的区间长度即可,只要余数相同,一相减长度为0就Ok了
AC code
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//并查集
int f[1];
int found(int k){
if(f[k] == k){
return k;
}
return f[k] = found(f[k]);
}
//阶乘
int factorial(int k){
if(k <= 1){
return 1;
}
else{
return k * factorial(k - 1);
}
}
int nums[50005];
int from[10];
int to[10];
int main()
{
ios::sync_with_stdio(false);
int n = r;
for(int i = 1; i <= n; i++){
nums[i] = r;
nums[i] = (nums[i] + nums[i-1]) % 7;
}
for(int i = 1; i <= n; i++){
to[nums[i]] = i;
}
for(int i = n; i >= 1; i--){
from[nums[i]] = i;
}
int ans = 0;
for(int i = 0; i <= 6; i++){
if(from[i] != 0){
ans = max(to[i] - from[i],ans);
}
}
cout<< ans <<endl;
return 0;
}
2、地毯
题目背景
此题约为NOIP提高组Day2T1难度。
题目描述
在 n×n 的格子上有 m 个地毯。
给出这些地毯的信息,问每个点被多少个地毯覆盖。
输入格式
第一行,两个正整数 n,m。意义如题所述。
接下来 m 行,每行两个坐标 (x1,y1) 和 (x2,y2),代表一块地毯,左上角是 (x1,y1),右下角是 (x2,y2)。
输出格式
输出 n 行,每行 n 个正整数。
第 i 行第 j 列的正整数表示 (i,j) 这个格子被多少个地毯覆盖。
输入输出样例
输入 #1
5 3
2 2 3 3
3 3 5 5
1 2 1 4
输出 #1
0 1 1 1 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1
说明/提示
样例解释
覆盖第一个地毯后:
0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|
0 | 1 | 1 | 0 | 0 |
0 | 1 | 1 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
覆盖第一、二个地毯后:
0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|
0 | 1 | 1 | 0 | 0 |
0 | 1 | 2 | 1 | 1 |
0 | 0 | 1 | 1 | 1 |
0 | 0 | 1 | 1 | 1 |
覆盖所有地毯后:
0 | 1 | 1 | 1 | 0 |
---|---|---|---|---|
0 | 1 | 1 | 0 | 0 |
0 | 1 | 2 | 1 | 1 |
0 | 0 | 1 | 1 | 1 |
0 | 0 | 1 | 1 | 1 |
数据范围
对于 20% 的数据,有 n≤50,m≤100。
对于 100% 的数据,有 n,m≤1000。
这题就是很典型的差分与前缀和的应用,因为他不断对区间进行加法,所以我们只需要控制区间的两头即可,利用差分,我们在区间的开头+1,在区间的结尾之后-1,这样之后我们用前缀和处理一下,就能得到这个区间的值
AC code
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//并查集
int f[1];
int found(int k){
if(f[k] == k){
return k;
}
return f[k] = found(f[k]);
}
//阶乘
int factorial(int k){
if(k <= 1){
return 1;
}
else{
return k * factorial(k - 1);
}
}
int mp[1009][1009];
int main()
{
ios::sync_with_stdio(false);
int n = r;
int m = r;
for(int i = 1; i <= m; i++){
int x1 = r;
int y1 = r;
int x2 = r;
int y2 = r;
for(int i = x1; i <= x2; i++){
mp[i][y1]++;
mp[i][y2+1]--;
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
mp[i][j] = mp[i][j-1] + mp[i][j];
cout<<mp[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
3、[HNOI2003]激光炸弹
题目描述
一种新型的激光炸弹,可以摧毁一个边长为 m 的正方形内的所有目标。现在地图上有 n 个目标,用整数 xi , yi 表示目标在地图上的位置,每个目标都有一个价值 vi .激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为 m 的边必须与 x 轴, y 轴平行。若目标位于爆破正方形的边上,该目标不会被摧毁。
现在你的任务是计算一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式
输入的第一行为整数 n 和整数 m,
接下来的 n 行,每行有 3 个整数 x,y,v,表示一个目标的坐标与价值。
输出格式
输出仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过 32767 )。
输入输出样例
输入 #1
2 1
0 0 1
1 1 1
输出 #1
1
说明/提示
数据规模与约定
- 对于 100% 的数据,保证 1≤n≤104,0≤*xi*,*yi*≤5×103,1≤m≤5×10^3,1≤vi<100。
这道题是只需要前缀和即可,通过前缀和计算大小为m的正方形内的价值和,然后搜索出最大的即可
AC code
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//并查集
int f[1];
int found(int k){
if(f[k] == k){
return k;
}
return f[k] = found(f[k]);
}
//阶乘
int factorial(int k){
if(k <= 1){
return 1;
}
else{
return k * factorial(k - 1);
}
}
int ans = 0;
int mp[5008][5008];
int main()
{
ios::sync_with_stdio(false);
int n = r;
int m = r;
for(int i = 0; i < n; i++){
int x = r;
int y = r;
int v = r;
x++;
y++;
mp[x][y] = v;
}
for(int i=1;i<=5005;i++){
for(int j=1;j<=5005;j++){
mp[i][j] = mp[i-1][j] + mp[i][j-1]- mp[i-1][j-1] + mp[i][j];
}
}
for(int i = m ; i <= 5005; i++){
for(int j = m ; j <= 5005; j++){
ans = max(ans,mp[i][j] + mp[i-m][j-m] - mp[i-m][j] - mp[i][j-m]);
}
}
cout << ans;
return 0;
}
4、[Poetize6] IncDec Sequence
题目描述
给定一个长度为 n 的数列 a1,a2,⋯,an,每次可以选择一个区间[l,r][l,r],使这个区间内的数都加 1 或者都减 1。
请问至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列有多少种。
输入格式
第一行一个正整数 n
接下来 n 行,每行一个整数,第 i+1行的整数表示 ai。
输出格式
第一行输出最少操作次数
第二行输出最终能得到多少种结果
输入输出样例
输入 #1
4
1
1
2
2
输出 #1
1
2
说明/提示
对于 100% 的数据, n≤100000,0≤ai≤2^31。
这题是比较明显的差分,因为我们要处理的信息显然是数字之间的差,所以我们先进行差分处理,然后找到正数之和和负数之和显然我们每次操作可以让正数和负数同时减一,减到0之后可以继续操作另外一个数,所以要操作多少次取决于正数之和和负数之和的绝对值中更大的那个。有多少种结果则取决于二者之差,因为当一个归0后另一个的值是多大就意味着有多少种情况(因为既可以是大的数减少,也可以是小的数变大,最后结果为0到这个数之间)
AC code
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//并查集
int f[1];
int found(int k){
if(f[k] == k){
return k;
}
return f[k] = found(f[k]);
}
//阶乘
int factorial(int k){
if(k <= 1){
return 1;
}
else{
return k * factorial(k - 1);
}
}
long long nums[100005];
long long x,y;
int main()
{
ios::sync_with_stdio(false);
int n = r;
for(int i = 1; i <= n; i++){
nums[i] = r;
}
for(int i = 2; i <= n; i++){
int t = nums[i] - nums[i-1];
if(t > 0){
x += t;
}
if(t < 0){
y -= t;
}
}
cout<<max(x,y)<<endl;
cout<<abs(x-y)+1<<endl;
return 0;
}
5、最大正方形
题目描述
在一个n*m的只包含0和1的矩阵里找出一个不包含0的最大正方形,输出边长。
输入格式
输入文件第一行为两个整数n,m(1<=n,m<=100),接下来n行,每行m个数字,用空格隔开,0或1.
输出格式
一个整数,最大正方形的边长
输入输出样例
输入 #1
4 4
0 1 1 1
1 1 1 0
0 1 1 0
1 1 0 1
输出 #1
2
这道题其实和第三题激光炸弹非常相似,只需要遍历出正方形区域内的值,当他等于边长的平方时就输出。因为数据量不是很大,我们可以直接暴力
AC code
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define r read()
#define ll long long
#define ull unsigned long long
#define mod 1000000007
using namespace std;
int s1[100009][100];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//辗转相除法
int gcd(int p,int q){
int t = p % q;
return t==0?q:gcd(q,t);
}
//阶乘
int mp[105][105];
int fac(int k){
int ans = 1;
for(int i = 1; i<= k; i++){
ans *= i;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
int n = r;
int m = r;
for(int i =1; i <= n; i++){
for(int j = 1; j <= m; j++){
mp[i][j] = r;
mp[i][j] = mp[i-1][j] + mp[i][j-1] - mp[i-1][j-1] + mp[i][j];
}
}
int ans = 0;
int k = min(n,m);
int i = 0;
for(i = k; i >= 1; i--){
for(int j = i; j <= n; j++){
for(int p = i; p <= m; p++){
ans = mp[j-i][p-i] + mp[j][p] - mp[j-i][p] - mp[j][p-i];
if(ans == i * i){
break;
}
}
if(ans == i * i){
break;
}
}
if(ans == i * i){
cout<<i<<endl;
break;
}
}
return 0;
}
三、小结
前缀和与差分是对数列的预处理,它更多是作为一种工具来辅助我们写其他题,当我们需要对区间求和,或是反复对区间加减的时候就可以使用这种方法