首页 > 其他分享 >前缀和,差分与离散化

前缀和,差分与离散化

时间:2025-01-11 13:32:35浏览次数:3  
标签:前缀 int 差分 离散 prefix x2 diff

前缀和:前缀和顾名思义一般是指数组中前n项的和,通常用另一个数组来存储,如定义一个前缀和数组prefix[n]表示的是a1+a2+--an,这与等差数列中的Sn有着相像之处,但如何用代码实现的呢?

只需要定义一个前缀和数组--prefix[n]

for(int i=1;i<=n;i++)
{
  prefix[i]=prefix[i-1]+a[i];
}

这样就以O(n)的复杂度求了前缀和数组,那么这有什么用呢?

前缀和数组求的是前n项的和,是否可以求特定区间段的和呢?答案是可以,如我要求a[n]数组中区间[L,R]的和,那么结果就是前R项的和减去前L-1项的和,用前缀和数组表示就是prefix[R]-prefix[L-1],这样就以O(1)的复杂度求得了特定区间的和

利用这个技巧就可以解决洛谷P8218 【深进1.例1】求区间和,代码如下

#include<stdio.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
int a[N];
ll prefix[N];
int main()
{
	int n; scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		prefix[i] = prefix[i - 1] + a[i];
	}
	int m; scanf("%d", &m);
	while (m--)
	{
		int l, r; scanf("%d %d", &l, &r);
		printf("%lld\n", prefix[r] - prefix[l - 1]);
	}
	
	return 0;
}

这就是最简单的前缀和啦,但这只是一维上的前缀和,如果要求二维上的前缀和呢?如矩阵上的某块区域上的和呢?

如定义一个二维数组a[n][n],给定一个坐标(x,y)求(1,1)到(x,y)的矩形内所有元素的和,定义一个二维前缀和prefix[n][n],可以从(1,1)递推到(n,n)

for(int i=1;i<=n;i++)
{
  for(int j=1;j<=n;j++)
  {
    prefix[i][j]=prefix[i-1][j]+prefix[i][j-1]-prefix[i-1][j-1]+a[i][j];
   }
}

这就是二维前缀和的递推过程

从前向后递推如果我要求prefix[x][y],那么prefix[x-1][y]和prefix[x][y-1]我一定已经求过,如上图阴影部分,阴影重叠部分为prefix[x-1][y-1],加了两次,要减去一个,再加上a[x][y],就可求得prefix[x][y]

那么怎么求特定矩阵的和呢?依旧要从二维前缀和上考虑,如我要求(x1,y1)到(x2,y2)的矩阵上的和

可以从面积上考虑,代码如下

ans=prefix[x2][y2]-prefix[x1-1][y2]-prefix[x2][y1-1]+prefix[x1-1][x2-1];

图解

考虑重叠部分为prefix[x1-1][y1-1]被减了两回,要加回来一个

这就是二维前缀和的基础使用啦,会了这个,就可以尝试解决蓝桥3811闪耀的灯光

以下为AC代码,要注意的是每次操作前后没有关联

#include<iostream>
using namespace std;
using ll = long long;
const int N = 305;
ll a[N][N];
ll prefix[N][N];
int main()
{
	ll n, m, c; cin >> n >> m >> c;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> a[i][j];
	    prefix[i][j]=prefix[i-1][j]+prefix[i][j-1]-prefix[i-1][j-1]+a[i][j];
		}
	}
	int t; cin >> t;
	while (t--)
	{
		ll x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
		ll k; cin >> k;
		k = k % (c + 1);
		int num = (x2 - x1 + 1) * (y2 - y1 + 1) * k;
    ll ans=prefix[x2][y2]-prefix[x1-1][y2]-prefix[x2][y1-1]+prefix[x1-1][y1-1];
		cout << ans - num << endl;
	}
	return 0;
}

然后就是差分:

差分一般是对数组进行差分,如定义一个差分数组diff[n]对a[n]进行差分,diff[i]=a[i]-a[i-1],

for(int i=1;i<=n;i++)
{
  diff[i]=a[i]-a[i-1];
}

那差分有什么用呢?差分可以让某一个区间加上一个 数值d,并且差分是可叠加的,对某一区间加d的操作为

diff[l]+=d,diff[r+1]-=d;

 看到这可能会奇怪这怎么就给区间加上d呢?这时区间还没有变化,在差分结束后需要对差分数组

做一个前缀和回复给原数组

for(int i=1;i<=n;i++)
{
  a[i]=a[i-1]+diff[i];
}

 开出的数组diff[]是为了记录每一个a[i]与a[i-1]的差,对diff[l]+=x,diff[r+1]-=x
是对从l到r的区间都加上x,再通过前缀和还原为原数组,a[i]=a[i-1]+diff[i],当i等于l时
,a[l]就多了一个x,后继因为a[l+1]=a[l]+diff[l+1],a[l]多x,则a[l+1]多x依次类推,直到a[r+1]=a[r]+diff[r+1],a[r]多了x,diff[r+1]少了x,则a[r+1]恢复正常

并且差分操作前后无影响,但在多次差分的过程中不可以查看此时a[n]具体的值

到这可以看出差分与前缀和的一些关系,如果我对a[n]求前缀和,得到前缀和数组prefix[n],那么反过来看我对prefix[n]进行差分,即prefix[i]-prefix[i-1]=a[i],prefix[i]的差分数组即为a[n],对a[n]进行

差分得到diff[n],diff[n]求前缀和就得到了a[n],二者可逆

此外还有一种对区间加上某值的方法,直接初始化diff[n]全为0,不对其求a[n]的差分,直接对diff[l]+=d,diff[r+1]-=d;最后对diff[n]做前缀和,会发现除了标记的[l,r]区间内加上了d,其余diff[i]还是

0,可以对每个a[i]加上diff[i]即为值

while(t--)
{
  cin>>d;
  diff[l]+=d;
  diff[r+1]-=d;
} 
for(int i=1;i<=n;i++)
{
  diff[i]+=diff[i-1];
  a[i]+=diff[i];
}

-这时可以尝试蓝桥1276小明的彩灯

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
long long a[N];
long long  diff[N];
void solve(int n,int q)
{
  for(int i=1;i<=n;i++)diff[i]=a[i]-a[i-1]; 
  while(q--)
  {
    long long  l,r,x;
    cin>>l>>r>>x;
    diff[l]+=x;
    diff[r+1]-=x;
  }
  for(int i=1;i<=n;i++)
  {
    a[i]=a[i-1]+diff[i];
  }
  for(int i=1;i<=n;i++)
  {
    if(a[i]<0)
    {
      a[i]=0;
    }
    cout<<a[i]<<" ";
  }
}
int main()
{ 
   int n,q;
   cin>>n>>q;
   for(int i=1;i<=n;i++)
   {
     cin>>a[i];
   }
   solve(n,q);
  return 0;
}

蓝桥3291区间更新

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+3;
int a[N]={0};
int diff[N];
void solve(int n,int m)
{
  for(int i=1;i<=n;i++)
  {
    cin>>a[i];
  }
  for(int i=1;i<=n;i++)//差分
  {
     diff[i]=a[i]-a[i-1];
  }
  while(m--)//对区间进行m次操作,得到最后的结果
  {
     int x,y,z;
     cin>>x>>y>>z;
     diff[x]+=z;
     diff[y+1]-=z;
  }
  for(int i=1;i<=n;i++)//前缀和还原
  {
    a[i]=a[i-1]+diff[i];
  }
  for(int i=1;i<=n;i++)//每次输出
   cout<<a[i]<<" \n"[i==n];
  }

int main()
{ 
  int n,m;
   while(cin>>n>>m)//多次输入
   {
     solve(n,m);
   }
  return 0;
}

接下来是二维差分,对矩阵某一块加上d

定义diff[n][n],依旧从面积上考虑,对diff[x][y]+=d后,进行前缀和,其实是对从a[x][y]到a[n][n]的

矩形都加上了d,代码如下

        diff[x1][y1]+=d;
	    diff[x2+1][y1]-=d;
		diff[x1][y2+1]-=d;
		diff[x2+1][y2+1]+=d;

图解

考虑重叠部分,所以diff[x2][y2]+=d;

就可以写蓝桥18440二维差分啦,AC代码如下

//二维差分
#include<bits/stdc++.h>
using namespace std;
int a[1005][1005];
int diff[1005][1005];
int main()
{
	int n,m,t;cin>>n>>m>>t; 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
		}
	}
	int x1,y1,x2,y2,d;
	while(t--)
	{
		cin>>x1>>y1>>x2>>y2>>d;
	    diff[x1][y1]+=d;
	    diff[x2+1][y1]-=d;
		diff[x1][y2+1]-=d;
		diff[x2+1][y2+1]+=d;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			diff[i][j]+=diff[i-1][j]+diff[i][j-1]-diff[i-1][j-1];
			a[i][j]+=diff[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cout<<a[i][j]<<" ";
		}
		cout<<endl;
	}
	return 0;
} 

 最后是离散化:

离散化概述:

离散化是程序设计中一个非常常用的技巧,它可以有效的降低时间复杂度。其基本思想就是在众多可能的情况中“只考虑我需要用的值”。离散化可以改进一个低效的算法,甚至实现根本不可能实现的算法。要掌握这个思想,必须从大量的题目中理解此方法的特点。

以下是一个简单的例题

这道题有很多种写法,以下提供两种

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
pair<int,int> p[N];
int a[N];
int main()
{
	int n;cin>>n;
    for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		p[i]={a[i],i};
	 } 
	sort(p+1,p+1+n);
	int rk=0;
	for(int i=1;i<=n;i++)
	{
		if(i==1||p[i].first!=p[i-1].first)
		{
			rk++;
		}
		a[p[i].second]=rk;
	}
	for(int i=1;i<=n;i++)cout<<a[i]<<" ";
	return 0;
 } 

利用pair分别记录值和下标,排序即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int> num;
int a[100005];
int main()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)
	{
	   cin>>a[i];
	   num.push_back(a[i]);
	}
	sort(num.begin(),num.end());
	num.erase(unique(num.begin(),num.end()),num.end());
	for(int i=1;i<=n;i++)
	{
		int rk=lower_bound(num.begin(),num.end(),a[i])-num.begin();
		cout<<rk+1<<" ";
	}
	return 0;
}

 利用c++库函数,排序后去重再二分查找

标签:前缀,int,差分,离散,prefix,x2,diff
From: https://blog.csdn.net/zjy200646/article/details/145066886

相关文章

  • 随笔:我为什么没有把《P5369 [PKUSC2018] 最大前缀和》做出来
    这是一篇随笔(绝对不是某CC风格的随笔)特别提醒:某W同学,再被【数据删除】要求写【数据删除】时你可以看一看这个大纲。我在干什么我在考【数据删除】时,开完题目后,我断定我就要解决这一道题。看见\(20\)这个小范围以后我就想起上一把【数据删除】的T【数据删除】。我就想DP......
  • 704 二维差分
    //704二维差分.cpp:此文件包含"main"函数。程序执行将在此处开始并结束。///*http://oj.daimayuan.top/course/22/problem/895给一个n×m的矩阵a1,1,a1,2,…,a1,m,…,an,m和q个修改操作。一开始所有位置都是0,每次修改给出五个数x1,y1,x2,y2,d,令所有ai,j(x1≤......
  • 高斯金字塔,高斯模糊,高斯差分
    高斯金字塔、高斯模糊和高斯差分是图像处理中非常重要的技术,常用于图像缩放、降噪、特征提取等领域。1.高斯模糊(GaussianBlur)高斯模糊是一种降噪技术,基于高斯函数的图像处理技术,用于平滑图像,减少噪声或细节。它在图像处理和计算机视觉中非常常用,尤其是在预处理步骤中,通过对图......
  • 208. 实现 Trie (前缀树)
    [题目链接](208.实现Trie(前缀树)-力扣(LeetCode))解题思路:前缀树,每个节点的内容:pre:经过该节点的数目;end:以该节点结尾的数目;nexts:下一条路径。前缀树有一个根节点,每次查找、插入、删除都要从这个节点开始。插入时,遍历该字符串,先从根节点开始,查看nexts是否有该字符,有就复......
  • 时序差分(Temporal Difference, TD)学习的详细介绍-ChatGPT4o作答
    时序差分(TemporalDifference,TD)学习的详细介绍时序差分(TemporalDifference,TD)是一种强化学习中重要的价值函数估计方法,结合了动态规划(DynamicProgramming)和蒙特卡洛方法(MonteCarlo)的优点。它通过从经验中直接学习预测值,而不需要完整的回报序列,能够高效地处理马尔科夫......
  • 并行前缀(Parallel Prefix)加法器
    并行前缀(ParallelPrefix)加法器并行前缀加法器的基本介绍二进制加法器是目前数字计算单元中的重要模块,基础的加法器架构包括行波进位加法器(RippleCarryAdder),超前进位加法器(CarryLook-AheadAdder),进位选择加法器(CarrySelectAdder)等。加法器的进位传播是其组合延迟的主要来源......
  • 703 二维前缀和
    //703二维前缀和.cpp:此文件包含"main"函数。程序执行将在此处开始并结束。///*http://oj.daimayuan.top/course/22/problem/894给一个n×m的矩阵a11,a12,…,a1m,…,anm和q个询问。每次询问给出四个数x1,y1,x2,y2,求∑i=x1~x2∑j=y1~y2a[ij]的值。输入格式第......
  • 蓝桥周赛差分问题
    题目如下 暴力不可取,学会差分然后前缀和,大家注意k的取值范围,要对26取余数,因为26为一个循环,我们只需要它的余数就可以了,我的差分数组减1操作,是因为我的下标是0开始的,如果从1开始,就不用减一,然后r的话就是diff[r+1]-=k,这样来写。  记住记住:一定要取26的余数!!!注意下标的选取......
  • 改进萤火虫算法之一:离散萤火虫算法(Discrete Firefly Algorithm, DFA)
            离散萤火虫算法(DiscreteFireflyAlgorithm,DFA)是萤火虫算法的一种重要变种,专门用于解决离散优化问题。一、基本概念        离散萤火虫算法将萤火虫算法的基本原理应用于离散空间,通过模拟萤火虫的闪烁行为来寻找全局最优解。在离散空间中,萤火虫的......
  • 模p^k的同余方程和离散对数求解
    modularEquation考虑求解多项式同余方程f(x)=0......