Part 2.8 前缀和 & 差分

题单

P3131 [USACO16JAN]Subsequences Summing to Sevens S

思路

题干很简单,就是要求最长的区间[l,r],满足7 | (a[l] + a[l + 1] + …… + a[r]),但是如果对每一个区间硬求和的话时间复杂度会加大,于是不难想到用线性前缀和。但是就算这样,发现时间复杂度还是高达O(n^2),于是应该还可以在优化。对于区间[l,r]如果7 | (pre[r] - pre[l - 1]),则不难得到pre[r]与pre[l - 1]在模7的意义下是相等的,而这个值只能是k,其中k属于{0, 1, 2, 3, 4, 5, 6}那我们便可对于每个k,在pre数组中找到下标最小(记为q)的和下标最大(记为h)的值为k的元素,如果能找到,那么ans = max (ans, h - j);否则直接跳过;但是需要警戒k=0的时候是特殊的,因为这个时候pre[i]与pre[j]其实已经被7整除了,那么ans = max (ans, j)。时间复杂度O(n)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50005, mod = 7;
int n, a[maxn], prea[maxn], ans = -1;
int q (int x) 
{
	for (int i = 1; i <= n; i++)
	{
		if (prea[i] == x) 
		{
			return i;
		}
	}
	return -1;
}
int h (int x) 
{
	for (int i = n; i >= 1; i--)
	{
		if (prea[i] == x) 
		{
			return i;
		}
	}
	return -1;
}
int main ()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%d", &a[i]);
		a[i] %= mod;
		prea[i] = (prea[i - 1] + a[i]) % mod;
	}
	for (int i = 0; i <= 6; i++) 
	{
		int qi = q (i), hi = h (i);
		if (qi == -1) 
		{
			continue;
		}
		if (i == 0) 
		{
			ans = max (qi, hi);
		}
		else 
		{
			ans = max (ans, hi - qi);
		}
	}
	printf ("%d", ans);
	return 0;
}

收获

水一个勤思考,以优化代码

P1387 最大正方形

思路

由题意即寻找边长最大的正方形,使得其中的元素全为1;进而可以转化为:寻找边长(记为l)最大的正方形,使得其中元素的和为l^2,求元素和,我们要是一个一个累加,则十分麻烦。于是想到用二维前缀和。然后我们有发现对于答案(即边长),它具有有界性与单调性:有界性,它肯定在[0, min (n, m)];单调性:对于边长大于答案的正方形,其一定不能满足题意又对边长进行二分答案,可以极大的简化时间复杂度,故我们作此操作时间复杂度:O(log (min (n, m)) * n * m)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
int n, m, a[maxn][maxn], pre[maxn][maxn], l, r, ans;
bool check (int x) 
{
	for (int i = 1; i + x - 1 <= n; i++) 
	{
		for (int j = 1; j + x - 1<= m; j++) 
		{
			int di = i + x - 1, dj = x + j - 1;
			if (pre[di][dj] - pre[i - 1][dj] - pre[di][j - 1] + pre[i - 1][j - 1] == x * x)
			{
				return true;
			}
		}
	}
	return false;
}
int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) 
	{
		for (int j  = 1; j <= m; j++) 
		{
			scanf ("%d", &a[i][j]);
			pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + a[i][j];
		}
	}
	l = 0, r = min (n, m);
	while (l <= r) 
	{
		int mid = (l + r) / 2;
		if (check (mid)) 
		{
			ans = mid;
			l = mid + 1;
		}
		else 
		{
			r = mid - 1;
		}
	}	
	printf ("%d", ans);
	return 0;
}

收获

学会用画图形象理解的方式,构建二位前缀和数组以及求矩形区域中的值。

P3397 地毯

思路

不难看出,这个题就是需要我们在适当的时间复杂度内,随着有关毯子信息的输入,动态地处理每一个坐标点被几个毯子覆盖,于是,我们想到用二维差分
。时间复杂度为O(n^2)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int n, m, a[maxn][maxn], diff[maxn][maxn];
int xi, xii, yi, yii;
int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf ("%d %d %d %d", &xi, &yi, &xii, &yii);
		diff[xi][yi] ++;
		diff[xi][yii + 1] --;
		diff[xii + 1][yi] --;
		diff[xii + 1][yii + 1] ++;
	}
	for (int i = 1; i <= n; i++) 
	{
		for (int j = 1; j <= n; j++)
		{
			a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + diff[i][j];
			printf ("%d ", a[i][j]);
		} 
		printf ("\n");
	}
	return 0;
}

收获

二维差分学习博客

P2280 [HNOI2003]激光炸弹

思路

发现与P1387 最大正方形有点相似,于是我们会选择用二维前缀和,但是我们这里不用二分答案了,因为正方形的边长已知,故我们只需要遍历一遍即可。但要注意m与我们的“价值图”的长和宽之间的大小以及数组越界问题(因为坐标点下标由0开始,而不是1)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005;
int n, m, pre[maxn][maxn], ans, xi, yi;
int x, y, v;
int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		scanf ("%d %d %d", &x, &y, &v);
		pre[x + 1][y + 1] += v; // 防止数组下标越界
		xi = max (xi, x + 1);
		yi = max (yi, y + 1);
	}
	xi = max (xi, m);  // 使得m大于整个“价值图”的长与宽时也能正常的枚举遍历。
	yi = max (yi, m); //同上
	for (int i = 1; i <= xi; i++)
	{
		for (int j = 1; j <= yi; j++) 
		{
			pre[i][j] += pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1];
		}
	}
	for (int i = 1; i + m - 1 <= xi; i++)
	{
		for (int j = 1; j + m - 1 <= yi; j ++) 
		{
			int dx = i + m - 1, dy = j + m - 1;
			v = pre[dx][dy] - pre[dx][j - 1] - pre[i - 1][dy] + pre[i - 1][j - 1];
			ans = max (ans, v);
		}
	}
	printf ("%d", ans);
	return 0;
}

收获

只能说,下次类似的题,要注意m与我们的“价值图”的长和宽之间的大小关系问题

P4552 [Poetize6] IncDec Sequence

偷懒

收获

这个题思路需要领会

最后的总结

这一小节我们学习了线性前缀和、线性差分、二维前缀和、二维差分,在其中屑觉得要重点理解二维差分。
前缀和用于预处理静态的某一数组,以便在我们想要获取某一区域内的值时,可以线性的获取;差分用于实现线性处理动态变化的数组;前缀和与差分互为逆过程。
有些题要求我们不能仅刻板的使用前缀和与差分,而是要在其知识的基础上让其根据妙用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值