前缀和与差分

前缀和与差分

1、二维差分

一维差分时是在需要更新的位置开始,不需要的位置结束,而二维的也一样。如果我们要在左上角是 (x1, y1),右下角是(x2,y2)的矩形区间每个值都+a,我们要在区间开始位置(x1,y1)处+a,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分-a消除+a的影响,而两个蓝色部分重叠的绿色部分多受了个-a的影响,所以绿色部分+a消除影响。

img

2、二维差分入门练习—P3397 地毯

P3397 地毯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

暴力可以做,复杂度n3勉强满足n,m <= 1000,使用二维前缀和和二维差分后,复杂度降至O(n2)。差分与前缀和互为逆运算,一维差分使用O(1)复杂度表示O(n)的覆盖,而二维差分使用O(1)的复杂度表示O(n2)的覆盖

#include<cstdio>
#include<iostream>
using namespace std;
long long m,n,a[1010][1010];

//直接模拟
void solve1(){
    cin>>n>>m;
    long long x1,y1,x2,y2;
    for(long long i=0;i<m;i++){
        cin>>x1>>y1>>x2>>y2;
        for(long long j=x1;j<=x2;j++){
            for(long long k=y1;k<=y2;k++) a[j][k]++;
        }
    }
    for(long long i=1;i<=n;i++){
        for(long long j=1;j<=n;j++) printf("%lld ",a[i][j]);
        printf("\n");
    }
}

//二维差分
void solve2(){
    cin>>n>>m;
    long long x1,x2,y1,y2;
    for(long long i=1;i<=m;i++){
        cin>>x1>>y1>>x2>>y2;
        a[x1][y1]++;
        a[x1][y2+1]--;
        a[x2+1][y1]--;
        a[x2+1][y2+1]++;
    }
    //逆向还原(求前缀和)
    for(long long i=1;i<=n;i++){
        for(long long j=1;j<=n;j++){
            a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
            printf("%lld ",a[i][j]);
        } 
        printf("\n");
    }
}

int main()
{
    solve2();
    system("pause");
    return 0;
}

3、一维差分练习—P3406 海底高铁

P3406 海底高铁 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这道题是一维前缀和/一维差分,采用差分思想O(m)读入,使用前缀和获取覆盖并进行方案选择花费O(n)。总的时间复杂度O(m+n)可以把前缀和与选择方案合二为一

#include <iostream>
#include <cmath>
using namespace std;

long long n,m,A,B,C,cover[100100];
long long dist=0;


void solve1(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	long long startx, endx;
	cin >> startx;
	//一维差分输入
	for(long long j = 2; j <= m; j++){
		scanf("%lld",&endx);
		if(startx < endx){
			cover[startx]++;
			cover[endx]--;
		}
		else{
			cover[endx]++;
			cover[startx]--;
		}
		//Swap(startx,endx);
		startx = endx;
	}
	//前缀和求覆盖+选择
	for(long long i = 1; i < n; i++){
        cover[i] += cover[i - 1];
		scanf("%lld%lld%lld",&A,&B,&C);
		dist += A*cover[i] < B*cover[i] + C ? A*cover[i] : B*cover[i] + C;
	}
}

int main()
{
	solve1();
    //system("pause");
    cout<<dist<<endl;
	return 0;
}

教训:

1、long long和int 不要混用,或许会带来精度的损失导致出错?这道题混用的记录如下记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

2、理解清楚题意再做,有些情况下没有AC并不是算法问题,可能是题目中的某一小步理解错误,比如这道题“从城市 P1出发分别按照P1,P2,P3…PM 的顺序访问各个城市”被我理解成“从城市1出发…”导致与最终结果总相差一个常数

3、长时间WR看答案后认为算法没问题就要仔细再读题,仔细看题解找不同。

4、差分思想优化—序列查询新解(CSP杂题)

计算机软件能力认证考试系统

注意到N在109量级,而n在105直接模拟会超时得到70分

思想:将0—N的模拟转化为0—n的等差数列求和。具体是按照这n个数进行分组,然后按照r个等差数列求和,最后减去多求的部分(代码中error -= fabs(first) * A_begin_rem + fabs(last) *A_end_rem)

#include <iostream>
#include <cmath>
using namespace std;

long long func(long long first, long long last, long long r)
{
	long long result;
	if (first * last >= 0)
	{
		result=fabs(first + last) * (fabs(last - first) + 1) / 2 * r;
	}
	else
	{
		result = (fabs(first) + 1) * fabs(first) / 2 * r + (last + 1) * last / 2 * r;
	}
	return result;
}

int main()
{

	int n, N;
	cin >> n >> N;
	long long r = N / (n + 1);

	long long A[n]={0};
	for (int i = 1; i <= n; ++i)
	{
		cin >> A[i];
	}
    A[n+1]=N;

	long long error = 0;
	for (int i = 0; i <= n; ++i)
	{
		long long begin_num = A[i] / r;
		long long end_num = (A[i + 1] - 1) / r;

		long long first = begin_num - i, last = end_num - i;

		error += func(first, last, r);

		long long A_bebin_rem = A[i] % r;
		long long A_end_rem = r - (A[i + 1] - 1) % r - 1;

		error -= fabs(first) * A_begin_rem + fabs(last) * A_end_rem;
	}

	cout << error << endl;
    //system("pause");
	return 0;
}

教训:

1、中间变量命名规范,便于阅读

2、写程序之前可以先在纸上演算,确定思路正确再写代码

3、适当空格、空行使代码看起来清晰

reference

二维差分 - 新之守护者 - 博客园 (cnblogs.com)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前缀和差分是一类常用的算法,它们常常被用来优化一些区间操作的问题,如求区间和、区间最大值/最小值等等。下面我们将分别介绍前缀和差分的定义、用法和常见问题。 ## 前缀和 前缀和,顾名思义,就是把前面所有数的和都求出来,用一个数组存起来,以便之后的查询。 ### 定义 给定一个长度为 $n$ 的序列 $a$,令 $s_i = \sum_{j=1}^{i}a_j$,则 $s$ 称为序列 $a$ 的前缀和数组。 ### 用法 前缀和的主要作用是用 $O(1)$ 的时间复杂度求出一个区间 $[l,r]$ 的和,即 $s_r - s_{l-1}$。这是因为 $s_r$ 存储了序列从 $1$ 到 $r$ 的和,而 $s_{l-1}$ 存储了序列从 $1$ 到 $l-1$ 的和,因此区间 $[l,r]$ 的和可以通过两个前缀和相减计算得出。 前缀和的时间复杂度为 $O(n)$,因为需要遍历一遍序列求出前缀和数组。但是,如果有多个查询需要求区间和,那么使用前缀和可以将每次查询的时间复杂度降低到 $O(1)$。 ### 代码实现 下面是使用前缀和求区间和的代码实现: ```cpp vector<int> a; // 原序列 vector<int> s(a.size() + 1); // 前缀和数组 // 计算前缀和 for (int i = 1; i <= a.size(); i++) { s[i] = s[i - 1] + a[i - 1]; } // 查询区间 [l, r] 的和 int sum = s[r] - s[l - 1]; ``` ## 差分 差分前缀和相反,它主要用来对区间进行修改。我们可以利用差分数组进行区间修改,并最终得到修改后的序列。 ### 定义 给定一个长度为 $n$ 的序列 $a$,令 $d_i = a_i - a_{i-1}$($d_1 = a_1$),则 $d$ 称为序列 $a$ 的差分数组。 ### 用法 差分的主要作用是对区间进行修改。假设我们需要将区间 $[l,r]$ 的数加上 $k$,我们可以将差分数组的 $d_l$ 加上 $k$,将 $d_{r+1}$ 减去 $k$。这样,对差分数组求前缀和,就可以得到修改后的序列。 具体来说,我们可以按照以下步骤进行区间修改: 1. 对差分数组的 $d_l$ 加上 $k$; 2. 对差分数组的 $d_{r+1}$ 减去 $k$; 3. 对差分数组求前缀和,得到修改后的序列。 差分的时间复杂度为 $O(n)$,因为需要遍历一遍序列求出差分数组。但是,如果有多次区间修改需要进行,那么使用差分可以将每次修改的时间复杂度降低到 $O(1)$。 ### 代码实现 下面是使用差分进行区间修改的代码实现: ```cpp vector<int> a; // 原序列 vector<int> d(a.size() + 1); // 差分数组 // 计算差分数组 for (int i = 1; i < a.size(); i++) { d[i] = a[i] - a[i - 1]; } // 修改区间 [l, r],将数加上 k d[l] += k; d[r + 1] -= k; // 对差分数组求前缀和,得到修改后的序列 for (int i = 1; i < d.size(); i++) { a[i] = a[i - 1] + d[i]; } ``` ## 常见问题 ### 1. 差分数组的长度是多少? 差分数组的长度应该比原序列长度多 1,因为 $d_1 = a_1$。 ### 2. 什么情况下使用前缀和?什么情况下使用差分? 如果需要进行多次区间查询,那么使用前缀和可以将每次查询的时间复杂度降低到 $O(1)$;如果需要进行多次区间修改,那么使用差分可以将每次修改的时间复杂度降低到 $O(1)$。 ### 3. 前缀和差分的本质区别是什么? 前缀和差分都是用来优化区间操作的算法,它们的本质区别在于: - 前缀和是通过预处理前缀和数组来优化区间查询; - 差分是通过预处理差分数组来优化区间修改。 ### 4. 前缀和差分能否同时使用? 当然可以。如果需要同时进行区间查询和修改,我们可以先使用差分数组对区间进行修改,然后再对差分数组求前缀和,得到修改后的序列。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值