【前缀和和差分】

1.一维前缀和

题目一

image-20220713192742418

#include<bits/stdc++.h>
const int N=1e5+10;
int array[N];
//一维前缀和
int sum[N];
int main()
{
    int m,n;
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        cin>>array[i];
        sum[i]=sum[i-1]+array[i];
    }
    while(m--)
    {
        int v,u;
        cin>>v>>u;
        cout<<sum[u]-sum[v-1]<<endl;
    }
    return 0;
}

2.二维前缀和

从左上角开始到矩阵某个点构成的子矩阵和。

image-20220713231953096

题目:子矩阵的和

image-20220713200223217

//子矩阵的和:s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]
const int N=1010;
int array[N][N];
int sum[N][N];  //记录前缀和

int main()
{
    int n,m,q,x1,y1,x2,y2;
    cin>>n>>m>>q;
    //向数组中加入数据
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>array[i][j];
        }
    }
    //求前缀和
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+array[i][j];
        }
    }
    while(q--)
    {
        cin>>x1>>y1>>x2>>y2;
        cout<<sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]<<endl;
    }
    return 0;
}

3.差分

image-20220713202703503

3.1一维差分

image-20220713203523969

题目描述

image-20220713231621031

//输入案例
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
//输出案例
3 4 5 3 4 2

*题目解析

image-20220713232642576
当需要在某个区间上加上某个数时。

image-20220713233542825

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int b[N];//构造一维差分数组
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        //构建差分数组
        b[i]=a[i]-a[i-1];
    }
    while(m--)
    {
        int l,r,c;
        cin>>l>>r>>c;
        b[l]+=c;
        b[r+1]-=c;
    }
    for(int i=1;i<=n;i++)
    {
        b[i]+=b[i-1];
        cout<<b[i]<<" 3
    }
    cout<<endl;
    return 0;
    //也可以写成
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        sum+=b[i];
        a[i]=sum;
    }
}
3.2二维差分

image-20220714194508389

image-20220714194402733

题目

image-20220714194848518

const int n=1e3+10;
//记录整数矩阵
int array[N][N];
//记录前缀和
int b[N][N];
void insert(int x1,int y1,int x2,int y2,int c)
{
    b[x1][y1]+=c;
    b[x1][y2+1]-=c;
    b[x2+1][y1]-=c;
    b[x2+1][y2+1]+=c;
}
int main()
{
    int n,m,q;
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>array[i][j];
            //初始化二维差分数组
            insert(i,j,i,j,array[i][j]);
        }
    }
    while(q--)
    {
        int x1,y1,x2,y2,c;
        cin>>x1>>y1>>x2>>y2>>c;
        insert(x1,y1,x2,y2,c);
    }
    //求二维差分的前缀和
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
          	//用b[i]作为最后记录前缀和的数组
            b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
            cin<<b[i][j]<<" ";
        }
    }
    cout<<endl;
    return 0;
}

4.RMQ问题与ST算法

image-20220714201118262

ST算法的本质是动态规划,时间复杂度是o(nlong)

image-20220714201514409

image-20220714202329718

题目

image-20220714203254456

//logn[i]记录区间长度为i时,对应x的值,其中 logn[i]=logn[i>>1]+1;
#include<bits/stdc++.h> 
using namespace std; 
const int N=1e6+10; 
const int LN=20; 
int logn[N]={-1};
int array[N];
//a[i][j]记录从i开始的2^j个数的区间最大值
int a[N][LN];
int qurey(int r,int l)
{
    int x=logn[l-r+1];
    //两个区间合并为[r,r+2^x]u[l-(1<<x)+1][x]==[r,l]
    return max(a[r][x],a[l-(1<<x)+1][x]);
}
int main()
{
    int m,n;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>array[i][j]; //输入数组
        //a[i][0]=array[0];
        a[i][0]=array[0];
        //记录区间长度为i对应的x值
        logn[i]=logn[i>>1]+1;
    }
    //a[i][j]记录从i开始的2^j个数的区间最大值,当i=0时,j的值最大为2^j;
    for(int j=1;(1<<j)<=n;j++)
    {
        //下标不能越界
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            //长度为2^j的区间差分为两部分
            a[i][j]=max(a[i][j-1],a[i+(1<<(j-1))][j-1]);
        }
    }
}

x.课后题目

4.1题目一

image-20220713205850304

#include<bits/stdc++.h>
const int N=1e5+10;
int b[N];
int main()
{
    int n;
    cin >> n;
    while (n)
    {
        memset(b, 0, sizeof(b));
        int r, l,m=0;
        while (m--)
        {
            cin >> l >> r;
            b[l] += 1;
            b[r + 1] -= 1;
        }
        for (int i = 1;i <= n;i++)
        {
            b[i] += b[i - 1];
            cout << b[i] << " ";
        }
        cout << endl;
        cin >> n;
    }
}
4.2题目二

image-20220714005750652

输入#1
10
5
890 965 256 419 296 987 45 676 976 742
输出#1
3813

题意可以转化为求长度为k+1的连续子数组的最大;

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int a[N]; //前缀和
int main()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        //求前缀和
        a[i]+=a[i-1]
    }
    int res=0;
    for(int i=1;i<=n-k;i++)
    {
        res=max(res,a[i+k]-a[i-1]);
    }
    cout<<res<<endl;
    return 0;
}
4.3宝石串(洛谷P2697)

题目描述

image-20220715010942092

/*
思路前缀和
把红宝石对应值1,绿宝石对应值-1;
求区间和为0的最长长度
*/

const int N=1000005;
int a[N];
int main()
{
    a[0]=0;
    string s;
    cin>>s;
    int len=s.size();
     int res=0;
    for(int i=1;i<=len;i++)
    {
        if(s[i-1]=='G')
        {
            a[i]=a[i-1]+1;
        }
        else if(s[i-1]=='R')
        {
            a[i]=a[i-1]-1;
            if(a[i]==0)
            {
                res=max(res,i);
            }
        }
    }
    for(int i=1;i<=len;i++)
    {
        for(int j=i+1;j<=len;j++)
        {
            if(a[j]-a[i-1]==0)
            {
                res=max(res,j-i+1);
            }
        }
    }
    cout<<res<<endl;
    return res;
}

image-20220715015526892

题目4.4

image-20220715020825242

#include<iostream>
using namespace std;
const int N=100010;
int a[N]={0};
int b[N]{0};
int main()
{
    int n=0,res=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=b[i-1]+a[i];
    }
    //男生和女生的数目要相等,也就是区间长度是区间和的2倍
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(2*(b[j]-b[i-1])==j-i+1)
            {
                res=max(res,j-i+1);
            }
        }
    }
    cout<<res<<endl;
    return 0;
}
4.5题目5:连续自然数和

image-20220715025035947

#include<iostream>
using namespace std;
const int N=1000010;
int a[N];
int main()
{
    a[0]=0;
    int m;
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        a[i]=a[i-1]+i;
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(a[i]-a[j-1]==m)
            {
                cout<<j<<" "<<i<<endl;
            }
        }
    }
    return 0;
}
//会超时,在内层循环可以使用二分查找进行优化
int find(int left,int right,int k)
{
    if(left>right) 
        return -1;
    int mid=(left+right)/2;
    if(s[mid]-k==m) return mid;
    else if(s[mid]-k>m) check(ll,mid-1,k);
    else if(s[mid]-k<m) check(mid+1,rr,k);
}
int main(){
    cin>>m;
    a[0]=0;
    for(int i=1;i<=m;i++)
    {
        a[i]=a[i-1]+i;
    }
    for(int i=1;i<=m;i++)
    {
        int k=find(i+1,m,a[i-1]);
         cout<<i<<" "<<k<<endl;
    }
    return 0;
}
4.6题目:最大正方形

image-20220715104933841

//方法一:二维前缀和
const int N=105;
int a[N][N];
int sum[N][N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>a[i][j];
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
        }
    }
    for(int k=1;k<=min(n,m);k++){  //遍历边的长度
        for(int i=k;i<=n;i++)
        {
            for(int j=k;j<=m;j++)
            {
                int sq1=sum[i][j]-sum[i-k][j]-sum[i][j-k]+sum[i-k][j-k];
                int sq2=i*j-(i-k)*j-i*(j-k)+(i-k)*(j-k);
                if(sq1==sq2)
                {
                    res=max(res,sq1);
                }
            }
        }
    }
    cout<<res<<endl;
    return res;
}
//动态规划
/*
	f[i][j]表示以(i,j)为右端点的最大正方形边长。
	当f[i][j]==1时,更新
	状态转移方程:f[i][j]=min(f[i-1][j],min(f[i][j-1],f[i-1][j-1]))+1;
	由于需要将三个点都进行覆盖,所以需要取min
*/
const int N=105;
int f[N][N];
int a[N][N];
int main()
{
    f[0][0]=0;
    int res=0;
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
       for(int j=1;j<=m;j++)
       {
            cin>>a[i][j];
        }
    }
   for(int i=1;i<=n;i++)
   {
       for(int j=1;j<=m;j++)
       {
           if(a[i][j])
           {
               f[i][j]=min(f[i-1][j],min(f[i][j-1],f[i-1][j-1]))+1;
               res=max(res,f[i][j]);
           }
       }
   }
    cout<<res<<endl;
    return 0;
}

image-20220715115927285

4.7题目7:区间权值

image-20220715120049307

#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int a[N];
int w[N];
int main()
{
    int n=0;
    cin>>n;
    a[0]=0;
    //记录数组的前缀和
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i]+=a[i-1];
    }
    for(int j=1;j<=n;j++){
        cin>>w[j];
    }
    //输出
    int res=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            res+=(a[j]-a[i-1])*w[j-i+1];
        }
    }
    cout<<res<<endl;
    return 0;
}
//上面如果数据量较大,肯定过不了;进行优化
//利用公式进行转换

image-20220715123805769

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
const int mod = 1e9 + 7;
int a[N], w[N], n;
int main() {
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] = (a[i - 1] + a[i]) % mod;
    }
    for(int i = 1; i <= n; i++) {
        cin >> w[i];
        w[i] = (w[i - 1] + w[i]) % mod;
    }
    int ans = 0;
    for(int i = 1; i <= n; i++) 
    {
        ans = (ans + 1ll * a[i] * w[i] - 1ll * a[i - 1] * w[n - i + 1]) % mod;
    }
    ans = (ans + mod) % mod;
    cout << ans << endl;
    return 0;
}
4.8题目8:毒瘤

image-20220715132653151

异或:相同为0,不同为1:

0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1)

解题思路
用前缀和预处理31位二进制数每一位1的个数,区间[L, R]上每一位如果1的个数多于0的个数,X对应二进制位上值为0,否则为1
注意若有多组可行解,需要输出较小的解,则当0和1个数一样时,X对应二进制位上取0

#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int n,q;
int l,r;
int a[N][35];   //a[i][0]记录数组的值,a[i][j]记录该数第j位的二进制位是多少
int sum[N][35]; //sum[i][0]记录数组的前缀和,sum[i][j]记录以i结尾,第j个二进制位有多少个1;
  
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i][0];
        for(int j=1;j<=31;j++)
        {
            a[i][j]=a[i][0]>>(j-1)&1;
        }
    }
    //记录前缀和
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=31;j++)
        {
            //记录到i结尾,有多少个1;
            sum[i][j]+=sum[i-1][j]+a[i][j];
        }
    }
    cin>>q;
    while(q--)
    {
        int x=0;
        int cnt=0;
        cin>>l>>r;
        //处理31个二进制位,第一个位符号位可以不做处理
        for(int i=1;i<=31;i++)
        {
            cnt=sum[r][i]-sum[l][i];
            if(2*cnt<r-l+1)
            {
                x+=1<<(i-1);
            }
        }
        cout<<x<<endl;
    }
    return 0;
}
4.9题目9:数学考试

image-20220715135405934

//方法前缀和
#include<bits/stdc++.h> 
using namespace std; 
typedef long long ll;
ll a[2000005];

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,k;
        cin>>n>>k;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            a[i]+=a[i-1]; //记录前缀和
        }
        ll res=-1e18;
        ll tmpmax=-1e18;
        //一次遍历同时维护两个最大值
        for(int i=k;i+k<=n;i++)
        {
            tmpmax=max(tmpmax,a[i]-a[i-k]); //记录长度位k的区间最大值
            //保证两个区间不能连续
            res=max(res,tmpmax+a[i+k]-a[i]);
        }
        cout<<res<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

影中人lx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值