二维前缀和详解 & LGOJ P2130 狂奔的Wzf 解题报告


关于二维前缀和,我们先从一道例题引入

例题引入

题目链接

LGOJ P2130 狂奔的Wzf

解题思路

嗯.看起来就是道广搜。
只要我们考虑怎么处理障碍,我们就可以很简单地套广搜模板上去。
设有障碍为1,无障碍为0,我们如何处理障碍?
我们可以很直观地想到,只要我要走的一条路 ( x s , y s )   t o   ( x e , y e ) (x_s,y_s)\ to\ (x_e,y_e) (xs,ys) to (xe,ye)上没有一个0,我们就可以往下走。我们可以想到使用前缀和来维护区间问题
先放代码自行体会,等下再来说二维前缀和。

详细代码

#define USEFASTERREAD 1

#include<cstdio>
typedef long long ll;
#define DEBUG printf("Passing Line %d in [%s]", __LINE__, __func__)
#define rg register
#define Rep(i, s, t) for(rg int i = s; i <= t; i++)
#define Repd(i, t, s) for(rg int i = t; i >= s; i--)

#if USEFASTERREAD 
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1<<20, stdin), tt == ss) ? EOF : *ss++)
#endif
struct IO {
	template<typename T> inline IO r(T& x)const {
		x = 0; T f = 1; char ch = getchar();
		for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
		for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
		x *= f; return *this;
	}
	inline IO r(char& x)const {
		x = getchar();
		while(x != '$' && x != '#' && x !='X' && x != '.') x = getchar();
		return *this;
	} 
	template<typename T> inline IO w(T x)const {
		if(x < 0) {putchar('-'); x = -x;}
		if(x >= 10) w(x / 10);
		putchar('0' + x % 10);
		return *this;
	}
	inline IO l()const {putchar('\n'); return *this;}
	inline IO s()const {putchar(' '); return *this;}
	template<typename T> inline IO wl(T x)const {w(x).l();return *this;}
	template<typename T> inline IO ws(T x)const {w(x).s();return *this;}
}io;
template<typename T> inline T Max(const T& x, const T& y) {return x < y ? y : x;}
template<typename T> inline T Min(const T& x, const T& y) {return x < y ? x : y;}
template<typename T> inline void Swap(T& x, T& y) {T t = x; x = y; y = t;}
template<typename T> inline T Abs(const T& x) {return x > 0 ? x : -x;}
#include<queue>
#include<cstring>
using namespace std;
struct Node {
	int x, y, st;
	Node(int x = 0, int y = 0, int st = 0) : x(x), y(y), st(st) {}
};
const int MAXN = 1005;
const int INF = 0x3f3f3f3f;
const int
	dx[4] = {1, -1, 0, 0},
	dy[4] = {0, 0, 1, -1};
char A[MAXN][MAXN];
int sum[MAXN][MAXN];
bool vist[MAXN][MAXN];
int tx, ty;
int n, m;
queue<Node> q; 
bool check(int x, int y) {
	return x >= 1 && x <= n && y >= 1 && y <= m;
}
int main() {
	io.r(n).r(m);
	Rep(i, 1, n)
		Rep(j, 1, m) {
			io.r(A[i][j]);
			if(A[i][j] == '#') tx = i, ty = j;
		}
	Rep(i, 1, n)
		Rep(j, 1, m) {
			if(i == 1 && j == 1) sum[i][j] = 0;
			else sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + (A[i][j] != '$' && A[i][j] != '#');
		}
	q.push(Node(1, 1, 0));
	while(!q.empty()) {
		Node now = q.front(); q.pop();
		int x = now.x, y = now.y, st = now.st;
		if(vist[x][y]) continue;
		vist[x][y] = 1;
		if(x == tx && y == ty) {
			io.wl(st);
			return 0;
		}
		for(rg int i = 0; i < 4; i++) {
			for(rg int k = 0; (1 << k) <= n || (1 << k) <= m; k++) {
				int nx = x + dx[i] * (1 << k), ny = y + dy[i] * (1 << k);
				if(check(nx, ny) && Abs(sum[nx][ny] - sum[nx][y - 1] - sum[x - 1][ny] + sum[x - 1][y - 1] == 0) && !vist[nx][ny])
					q.push(Node(nx, ny, st + 1));
			}
			
		}
	}
	return 0;
}

图文理解

递推求二维前缀和

我们的二维前缀和其实是维护从左上角(一般是 ( 1 , 1 ) (1,1) (1,1)开始,这样可以不用考虑边界)到当前的右下角 ( n , m ) (n,m) (n,m)的一个矩阵的元素的和 s u m n m sum_{nm} sumnm
s u m n m = ∑ i = 1 n ∑ j = 1 m A i j sum_{nm}=\sum_{i=1}^n\sum_{j=1}^mA_{ij} sumnm=i=1nj=1mAij
我们欲求
在这里插入图片描述
这块矩阵的元素和
考虑 s u m n m sum_{nm} sumnm如何通过递推得到
我们有 s u m n m = s u m ( n − 1 ) m + s u m n ( m − 1 ) − s u m ( n − 1 ) ( m − 1 ) + a n m sum_{nm}=sum_{(n-1)m}+sum_{n(m-1)}-sum_{(n-1)(m-1)}+a_{nm} sumnm=sum(n1)m+sumn(m1)sum(n1)(m1)+anm
为何如此?
我们从图形上理解。
我们把二维前缀和理解成矩阵的“面积”

在这里插入图片描述
S 蓝 色 矩 阵 = S 黄 色 矩 阵 + S 绿 色 矩 阵 − S 黑 色 矩 阵 + S 橙 色 矩 阵 S_{蓝色矩阵}=S_{黄色矩阵}+S_{绿色矩阵}-S_{黑色矩阵}+S_{橙色矩阵} S=S+S绿S+S
翻译过来就是
s u m n m = s u m ( n − 1 ) m + s u m n ( m − 1 ) − s u m ( n − 1 ) ( m − 1 ) + a n m sum_{nm}=sum_{(n-1)m}+sum_{n(m-1)}-sum_{(n-1)(m-1)}+a_{nm} sumnm=sum(n1)m+sumn(m1)sum(n1)(m1)+anm
有个求二维前缀和的口诀:上加左,减左上,加自己

O(1)求矩阵元素和

为什么我们要使用二维前缀和?主要还是为了可以 O ( 1 ) O(1) O(1)算出给定的一块矩阵的元素和
设我们求一块左上为 ( x , y ) (x,y) (x,y),右下为 ( n , m ) (n,m) (n,m)的矩阵的元素和。
同样,我们从图形的角度来理解
在这里插入图片描述
我们要求的是 S 红 色 矩 形 S_{红色矩形} S
根据图形,易得:
S 红 色 矩 形 = S 蓝 色 矩 形 − S 黄 色 矩 形 − S 绿 色 矩 形 + S 黑 色 矩 形 S_{红色矩形}=S_{蓝色矩形}-S_{黄色矩形}-S_{绿色矩形}+S_{黑色矩形} S=SSS绿+S
翻译一下,就是:
a n s = s u m n m − s u m ( x − 1 ) m − s u m n ( y − 1 ) + s u m ( x − 1 ) ( y − 1 ) ans=sum_{nm}-sum_{(x-1)m}-sum_{n(y-1)}+sum_{(x-1)(y-1)} ans=sumnmsum(x1)msumn(y1)+sum(x1)(y1)
忘记了的话,现场推就好了,画个图。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值