【习题集】(前缀与差分)(2213字)

目录

第一题:区间三等分

第二题:二维前缀和(最大子矩阵)​​​​​​

第三题:一维的前缀和(一道有趣的小题)

第四题:前缀+二分(其实可以不用)


第一题:区间三等分

数据范围很大,暴力显然超时,题目要求我们去求数组三等分的方法数,先进行前缀和处理,得到前缀和数组,问题就转化为,找到i, j其中i < j,3 * sum[i] = total_sum ,3 * sum[j] = total_sum * 2。这样只需要考察对于j前面有多少个i即可,虽然可以再次求前缀和不过没有必要。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e5 + 5;
long long sum[N];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin >> n;
	ll s = 0;
	for (int i = 0; i < n; i++) {
		int a;
		cin >> a;
		s += a;
		sum[i] = s;
	}
		int a = 0;
		ll ans = 0;
		for (int i = 1; i < n; i++) {
			if (sum[i - 1] * 3 == sum[n - 1]) {
				a++;
			}
			if (i < n - 1 && sum[i] * 3 == sum[n - 1] * 2) {
				ans += a;
			}
		}
		cout << ans << '\n';
	return 0;
} 

第二题:二维前缀和(最大子矩阵)​​​​​​

如果还要优化的话可以用二维的树状数组来解决。 

第一次处理:求出从边界(上和左)到某个点的子矩阵的和。

第二次处理:对于每个上述的矩阵求出所有可能的分割后的矩阵,因为最大的矩阵不一定会接着边缘。

记从1行1列到i行j列的矩阵为sum[i][j]其实这里也算是某种动态规划。

000
0sum[i - 1][j - 1]sum[i - 1][j]
0sum[i][j - 1]sum[i][j](未知)
000
0sum[k][l]sum[k][j]
0sum[i][l]sum[i][j]

 不难得到实现。

实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 150;
int sum[N][N], a[N][N];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> a[i][j];
		}
	}
	int ans = -INT_MAX; 
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			for (int k = 0; k <= i; k++) {
				for (int l = 0; l <= j; l++) {
					ans = max(ans, sum[i][j] - sum[k][j] - sum[i][l] + sum[k][l]);
				}
			}
		}
	}
	cout << ans << '\n';
	return 0;
}

第三题:一维的前缀和(一道有趣的小题)

求出总时间,然后求出最大的差即可求出答案,根据单调性只需要比较两个点之间的差即可。

#include <bits/stdc++.h>
using namespace std;
long long sum[1000001];
int main() {
	long long n, k, a, maxx = 0;
	cin >> n >> k;
	sum[1] = 0;
	for (int i = 2; i <= n; i++) {
		cin >> a;
		sum[i] = sum[i - 1] + a;
	}
	for (int i = k + 1; i <= n; i++) maxx = max(maxx, sum[i] - sum[i - k]);
	cout << sum[n] - maxx << '\n';
	return 0;
}

第四题:前缀+二分(其实可以不用)

这道题我们要关注的是刚好是平方数的临界状态,越过这个临界状后改变量会变化,表达式会改变。这里的时间复杂度设计得比较宽松,即使不用二分也可以过。

实现:

#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int N = 3000;
ll d[N], sum[N]; //d表示的第一个变化量为i的点值
int bs(ll x) {
	int l = 1, r = 2961, mid;
	while (l < r) {
		mid = l + r + 1 >> 1;//找到最后一个小于等于它的值
		if (d[mid] <= x) l = mid;
		else r = mid - 1; 
	}
	return l;
}
int main() {
	d[2] = 1;
	for (ll i = 3; i <= 2961; i++) {
		d[i] = i * i * 114 - 514;
	}
	sum[2] = 2;//前缀和,刚好到这里的和是多少,主意边界
	for (ll i = 3; i <= 2961; i++) {
		sum[i] = (d[i] - d[i - 1] - 1) * (i - 1) + sum[i - 1] + i;
	}
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while (t--) {
		ll n;
		cin >> n;
		int pos = bs(n);
		cout << sum[pos] + pos * (n - d[pos]) << '\n';//加上多出来的部分
	}
return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值