ET算法【前缀和与差分】

最近比较忙其实根本不知道在忙啥,好久没写代码了,今天在熄灯前20min水一篇博客估计要鸽到明天咯,近期刷洛谷题单有些刷不动了,感觉是因为那些题需要会的东西有点多,看题解也越来越看不懂了,还得找个课来学学新算法,等a协的培训又有些太慢了马上就要打新生赛了哇。这不就来了嘛,挑了挑还是这个便宜,内容好像大概有一半已经学过了?不懂,但我觉得还是选这个比较好,嗯,我觉得ok就ok,难得的freedom。

一、序

前缀和与差分,主要运用于多次求大区间范围内的和或一起调整元素大小,基础分为一维与二维

diff     --前缀和-->   a   --前缀和-->   prefix                             diff     <--差分--   a   <--差分--   prefix

注意a[ 0 ]不用,值设为0,从a[ 1 ]开始

二、一维

1.一维前缀和

P1049 - 【模板】前缀和 - ETOJ (eriktse.com)

数列总学过吧,an总会输入吧,Sn总会算吧。啊没错,前缀和就是Sn。

核心式:prefix[ i ]=prefix[ i-1 ]+a[ i ]

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define qio ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

const int N = 1e5 + 5;
ll a[N], prefix[N];       //考虑数据范围,Sn应该会超int的
int main() {
	qio
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	for (int i = 1; i <= n; i++)prefix[i] = prefix[i - 1] + a[i];
	int q, l, r;
	cin >> q;
	for (int i = 1; i <= q; i++) {
		cin >> l >> r;
		cout << prefix[r] - prefix[l-1] << "\n";  //注意l-1,第一次就漏了
	}
}

2.一维差分

P1050 - 【模板】差分 - ETOJ (eriktse.com)

差分就是前后两个数的差值,高中好像也经常用吧(?),差分数组的求和就是a[ i ]的值

核心式:diff[ i ] = a[ i ] - a[ i - 1 ]

手敲图解:

       啊这,还没敲就熄灯咯,阿巴阿巴,真鸽了啊,还不知道啥时候有时间接着写呢,刚开学一个月就要期中了,就离谱,咱也不知道什么时候有时间接着写,咕咕咕    接着写

    a0        a1        a2        a3        a4        a5        a6

                d1        d1        d1        d1        d1        d1

                            d2        d2        d2        d2        d2

                                        d3        d3        d3        d3

                                                    d4        d4        d4

                                                                d5        d5

                                                                            d6

想得到a的值,只需要把a下面的diff全都加起来就成(根据diff的定义),并且可以快速的区间修改,例如要给a2到a4这一区间加上x,只需让d2+x,d5-x即可,修改完后要使用前缀和重新累加得到新的a[ i ]。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define qio ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
const int N = 1e5 + 5;
ll a[N], prefix[N], diff[N];
int main() {
	qio
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) diff[i] = a[i] - a[i - 1];
	int m;
	cin >> m;
	int l, r, x;
	for (int i = 1; i <= m; i++) {
		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++) prefix[i] = prefix[i - 1] + a[i];	
	int q;
	cin >> q;
	for (int i = 1; i <= q; i++) {
		cin >> l >> r;
		cout << prefix[r] - prefix[l - 1] << "\n";
	}
	return 0;
}

三、二维

二维就稍微复杂一点了,我得想想怎么画出抽象而又美丽的图了~

1.一维前缀和

P1060 - 【模板】二维前缀和 - ETOJ (eriktse.com)

计算prefix核心式:

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

核心式:prefix[x2][y2] - prefix[x2][y1 - 1] - prefix[x1 - 1][y2] + prefix[x1 - 1][y1 - 1]

完整代码:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define qio ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
const int N = 1e3 + 5;
ll a[N][N], prefix[N][N], diff[N][N];
int main() {
	qio
	int n, m, q;
	cin >> n >> m >> q;
	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++) {
			prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1] + a[i][j];
		}
	}
	for (int i = 1; i <= q; i++) {
		int x1, y1, x2, y2, ans;
		cin >> x1 >> y1 >> x2 >> y2;
		ans = prefix[x2][y2] - prefix[x2][y1 - 1] - prefix[x1 - 1][y2] + prefix[x1 - 1][y1 - 1];
		cout << ans << "\n";
	}
}

接着咕咕咕,写的好慢怎么办啊,突然想起劳动通识课还没看呢,一天天的也不知道都干了啥就过完了,阿巴阿巴

2.二维差分

P1061 - 【模板】二维差分 - ETOJ (eriktse.com)

计算diff核心式:

for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			diff[i][j] = a[i - 1][j - 1] - a[i][j - 1] - a[i - 1][j] + a[i][j];
/*或者		diff[i][j]+=a[i][j];
			diff[i+1][j]-=a[i][j];
			diff[i][j+1]-=a[i][j];
			diff[i+1][j+1]+=a[i][j];		*/
		}
	}

核心式:

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

完整代码:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define qio ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

const int N = 1e3 + 5;
ll a[N][N], prefix[N][N], diff[N][N];
int main() {
	qio
	int n, m, q;
	cin >> n >> m >> q;
	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++) {
			diff[i][j] = a[i - 1][j - 1] - a[i][j - 1] - a[i - 1][j] + a[i][j];
/*			diff[i][j]+=a[i][j];
			diff[i+1][j]-=a[i][j];
			diff[i][j+1]-=a[i][j];
			diff[i+1][j+1]+=a[i][j];		*/
		}
	}
	for (int i = 1; i <= q; i++) {
		int x1, y1, x2, y2, c;
		cin >> x1 >> y1 >> x2 >> y2 >> c;
		diff[x1][y1] += c;
		diff[x1][y2 + 1] -= c;
		diff[x2 + 1][y1] -= c;
		diff[x2 + 1][y2 + 1] += c;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + diff[i][j];
			cout << a[i][j] << " ";
		}
		cout << "\n";
	}
}

四、应用

1.鼠鼠我鸭

P1034 - 鼠鼠我鸭 - ETOJ (eriktse.com)

最大字段和的求法,以及三目运算符的使用

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define qio ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
void solve();

const int N = 1e5 + 5;
ll a[N], w[N];
int main() {
    qio
	int p;
	cin >> p;
	while (p--) {
		solve();
	}
}

void solve() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)cin >> a[i];
	for (int i = 0; i < n; i++)cin >> w[i];
	//修改前的基本量
	ll ess = 0;
	for (int i = 0; i < n; i++) {
		ess += a[i] * w[i];
	}
	//ma为修改后区间的字段和,fix为所有修改后字段和中最大的一段
	ll fix = 0, ma = 0;
	for (int i = 0; i < n; i++) {
		ma = max(0ll, ma + (a[i] ? -1 : 1) * w[i]);
		fix = max(ma, fix);
	}
	cout << ess + fix << "\n";
}

2.卡牌游戏

P6625 [省选联考 2020 B 卷] 卡牌游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思维题,合并卡牌并没有改变其排序以及前缀和

#include<bits/stdc++.h>
using namespace std;
#define qio ios::sync_with_stdio(0), cin.tie(0),cout.tie(0);
typedef long long ll;
const int N = 2e5 + 10;
ll a[N];
ll prefix[N];
int main() {
	qio
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) prefix[i] = prefix[i - 1] + a[i];
	ll sum = 0;
	for (int i = 1; i <= n; i++) {
		if (i != 1 && prefix[i] >= 0) sum += prefix[i];
	}
	cout << sum << '\n';
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值