前缀和+差分(附带例题)

一维前缀和模板

前缀和就相当于数学的前n项和

这里可以手动模拟一下,就很清晰了

#include<bits/stdc++.h>
using namespace std;
const int N = 1000;
int a[N],s[N];
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++) //从下标1开始读入
        cin >> a[i];

    for(int i = 1; i <= n; i ++) //因为是全局变量s[0] = 0,前n项的和等于前n-1项加上第n个数
        s[i] = s[i-1] + a[i];
    
    //前缀和的作用是查询更快,时间复杂度O(1)
    int l , r;
    cin >> l >> r; //第l个数到第r个数之间的区间和
    cout << s[r] - s[l-1] << endl;
}

//原理可能讲得不是很清楚,自行查阅吧

P5638 【CSGRound2】光骓者的荣耀//一维例题

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
long long a[N];
long long n,k,m;
int main(){
	ios::sync_with_stdio(false);
	cin >> n >> k;
   /*这道题思路是说小k可以跳一次长度是K,那么你只需要寻找一段长度是K的最大时间段,然后减去就行了
     范围开个long long,防止数据溢出
   */
	for(int i = 1; i <= n-1; i ++) //
	 {  cin >> m;
	    a[i] = a[i-1] + m;
	 }
	
	long long ans = 0;
    
    for(int i = k; i < n; i ++) ans = max(ans,a[i] - a[i-k]);
	
	cout << a[n-1] - ans << endl;//总的时间减去长度是k的最大时间段
}

二维前缀和模板

#include<bits/stdc++.h>
using namespace std;
const int N = 10000; //二维前缀和的板子好像是dp推出来的
int a[N][N],s[N][N];
int main()
{  int n,m;
   ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
   cin >> n >> m; //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 ++)
     s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i-1][j-1];

   //查询x1,y1,x2,y2;
   cin >> x1 >> y1 >> x2 >> y2;
   cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];

   //查询某个区间的最大值
   int ans = -1;       //如果全是正数ans设置为-1,如果有负数,那么ans = -0x3f3f3f3f;

   for(int x1 = 1; x1 <= n; x1++)
    for(int y1 = 1; y1 <= m; y1 ++)
      for(int x2 = x1; x2 <= n; x2 ++)
        for(int y2 = y1; y2 <= m; y2 ++)
          { 
            if(s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] > ans)
             ans = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];
          }
   
   //这个查询有一个缺点,时间复杂度高O(n^4) 等我找到方法再更新

  return 0;
}

P2004 领地选择

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e3+10;
long long  s[N][N],a[N][N];
long long ans = -1e10;
int main(){
	int n,m,c;
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); 
	//上面的东西是读入加快相当于和scanf,printf速度差不多具体的自行百度哦 
	 
	cin >> n >> m >> c;
	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 ++)
	  { s[i][j] = s[i-1][j] + s[i][j-1] -s[i-1][j-1] + a[i][j];
	  }
	
	int x,y; //标记左上角的点 套用板子,注意一下小细节
	 //因为是一个正方形,只需要枚举一个点(x,y),另一个点是(x+c,y+c)
	for(int i = 1; i <= n; i ++) 
	 for(int j = 1; j <= m; j ++)           //i+c,j+c;
      {  if(i + c - 1<= n && j + c -1 <= m) //保证两个点的范围是小于n和m的 
      	 { if(s[i+c-1][j+c-1] - s[i-1][j+c-1] - s[i+c-1][j-1] + s[i-1][j-1] > ans)
      	   { ans = s[i+c-1][j+c-1] - s[i-1][j+c-1] - s[i+c-1][j-1] + s[i-1][j-1];
      	     x = i;
      	     y = j;
		   }
		 }
	  }
	
	cout << x << ' ' << y << endl;
	  
    return 0;	
}

一维差分

差分就是前缀和的逆运算,相当于函数的求导和积分

这个算法可以处理在一个区间范围内加入一个数,在一个区间内删除一个数

 给定一个数组在1 2 2 1 2 1 在下标1-3 之间每个数加1,在下标3 -5之间加1,在下标1-6加入1

朴素做法就是加for循环,然后再这个区间内加入,时间复杂度是O(n^2)

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N],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 ++)
    {
        a[i] = b[i] + a[i-1];
        cout << a[i] << ' ';
    }
    return 0;
}

- [P3406 海底高铁]

大致意思就是每次从一个站台l到另一个站台r,统计该站台经过的次数,最后比较该站台买卡实惠,还是买票实惠,如何计算经过的次数是本题的重点,利用差分的思想,在差分数组在左边l加1

,在右边的r出减1就行了,此题的差分数组不用构造了,之间默认每个数都是0

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e6+10;
LL s[N];
LL a[N];
LL ans,n,m;
int main()
{  cin >> n >> m;
   for(int i = 1; i <= m; i ++) cin >> a[i];
   
   for(int i = 1; i < m; i ++) 区间x到y,找到最大值和最小值
   { int x,y;
     x = min(a[i],a[i+1]);
     y = max(a[i],a[i+1]);
     s[x] += 1;
     s[y] -= 1;
   }
   for(int i = 1; i <= n-1; i ++)  //累加构造前缀和,时刻牢记差分是前缀和的逆运算
     s[i] += s[i-1];
   
   LL ans = 0;
   for(int i = 1; i < n; i ++)  
   {  LL A,B,C;                             //这里的LL是longlong类型,防止爆int
      cin >> A >> B >> C;
      LL p1 = A * s[i] ,p2 = B * s[i] + C;  //统计每个数的最优惠价格
	  ans += min(p1,p2);
   }
   cout << ans << endl;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值