下面笔者贴出学习大佬的博客地址:
前缀和问题、前缀和、二维前缀和与差分的小总结、二维前缀和详解
接下来是一个例题的参考博客:2019中山大学ACM校赛:二维前缀和二维差分
贴出上面博客的注释代码:
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<vector>
using namespace std;
//const int Max = 1e4; 这里使用会超出内存限制
//int record[Max][Max], glass[Max][Max];
int main(){
int n, m;
while(scanf("%d%d", &n, &m) == 2){
int mo, th;
//memset(record, 0, sizeof(record));
//memset(glass, 0, sizeof(glass));
vector<vector<int> > record(n + 2, vector<int>(m + 2)), glass(n + 2, vector<int>(m + 2));
//cin>>mo;使用这个比使用scanf更慢,所以这里会超时
scanf("%d", &mo);
int ldx, ldy, rux, ruy;
while(mo--){
scanf("%d%d%d%d", &ldx, &ldy, &rux, &ruy);
++record[ldx][ldy];
++record[rux+1][ruy+1];
--record[ldx][ruy+1];
--record[rux+1][ldy];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
record[i][j] += record[i-1][j] + record[i][j-1] - record[i-1][j-1];
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
glass[i][j] += glass[i-1][j] + glass[i][j-1] - glass[i-1][j-1] + (record[i][j] > 0 ? 1 : 0);
}
}
//cin>>th;
scanf("%d", &th);
while(th--){
scanf("%d%d%d%d", &ldx, &ldy, &rux, &ruy);
int ans = glass[rux][ruy] - glass[ldx-1][ruy] - glass[rux][ldy-1] + glass[ldx-1][ldy-1];
if(ans == (rux - ldx + 1)*(ruy - ldy + 1)){
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
}
}
return 0;
}
接下来列出在寒假牛客集训时的一道题目:关于字符串和前缀和问题
和在题解看到的大佬写的代码:大佬的题解
#include<iostream>
#include<string.h>
using namespace std;
int dp[200010][26] = {0};
int main(){
int n, k, i, j;
cin>>n>>k;
string s;
cin>>s;
dp[0][s[0]-'a'] = 1;
for(int i = 1; i < n; i++){
for(int j = 0; j < 26; j++){
dp[i][j] = dp[i-1][j];
}
dp[i][s[i]-'a']++;//这里用到了前缀和
}
int mi = 1e9;
for(int i = 0; i < 26; i++){
int temp = 0, t2 = 0;
if(dp[n-1][i] < k) continue;
while(temp<n && dp[temp][i] == 0) temp++;
while(t2<n && dp[t2][i] < k) t2++;//这里先找到这个子串含有k个该字母的第一个出现的位置
mi = min(mi, t2-temp+1);
for(temp++; temp < n; temp++){
if(s[temp-1]-'a' == i){//这里是去找剩下子串中满足条件的位置
t2++;
while(t2<n && s[t2]-'a'!=i) t2++;
if(t2 == n) break;
}
mi = min(mi, t2-temp+1);//这里找到满足条件的最小的值
}
}
if(mi == 1e9) cout<<-1<<endl;
else cout<<mi<<endl;
return 0;
}
下面是今天题目中遇到的前缀和问题:算概率
其实这题考的主要是前缀和问题和概率论里面的如何计算概率,基本上是数学问题,但是还有一个笔者之前不知道的知识盲区:逆元
该题题目为:
这里附上大佬的题解:题解
这题中的逆元体现在怎么去求出不正确的概率,因为在这题中,输入的概率不是普通的概率,而是在mod意义下给出的,所以不正确的概率是mod+1-pi。而普通的不正确概率为1-pi
并且在这题中,还有自己的转移方程,看题解中
所以最后有这些上面大佬的代码:
#include<iostream>
#include<stdio.h>
using namespace std;
const int N = 2005, mod = 1e9+7;
long long n, p[N], f[N][N];
int main(){
cin>>n;
for(int i = 1; i <= n; i++){
cin>>p[i];
}
for(int i = f[0][0] = 1; i <= n; i++){//这里是求前缀和
f[i][0] = f[i-1][0]*(mod + 1 - p[i]);//i题里有0题正确的概率,即都错误的概率
for(int j = 1; j <= i; j++){
f[i][j] = f[i-1][j]*(mod+1-p[i]) + f[i-1][j-1]*p[i];
//i题里面有j题正确的概率=i-1里面已经有了j题正确,第i题要为错误 + i-1题里面只有j-1题正确,所以第i题必须为正确
}
}
for(int i = 0; i <= n; i++){//最后输出即可
cout<<f[n][i]<<" ";
}
return 0;
}
前缀和中找相同的数的问题,可以通过双指针来解决,复杂度更理想:
题目
这里可以通过双指针去查找相同的数,接下来贴出代码:
#include<iostream>
#include<string>
using namespace std;
int a[100005], b[100005];
int main(){
int n, m, x, y;
cin>>n>>m;
for(int i = 1; i <= n; i++){//求前缀和
cin>>x;
a[i] = a[i-1] + x;
}
for(int i = 1; i <= m; i++){
cin>>y;
b[i] = b[i-1] + y;
}
int i = 1, j, ans = 0;
int n1 = 1, n2 = 1;
while(n1 <= n && n2 <= m){//当其中有一个条件不满足时,退出循环
if(a[n1] < b[n2]) n1++;//这里去判断是哪个小了,则让哪个下标加一
else if(a[n1] > b[n2]) n2++;
else{//如果相等,则分的段数也加一,且俩个数组的下标也分别往后移动
ans++;
n1++;
n2++;
}
}
/*for(; i <= n; i++){
for(j = 1;j <= m; j++){
if(a[i] == b[j]){
ans++;
break;
}
}
}*/
cout<<ans<<endl;
return 0;
}