二维前缀和

二维前缀和

众所周知,一维前缀和是用o(1)复杂度来处理线性数据区间和的问题的。那二维前缀和就是通过o(1)复杂度来处理子矩阵和问题的好办法。

1、前缀和数组预处理:

​ 一维前缀和数组中pi的意义是a1到ai的和,二维前缀和中pij的意义是a11到aij这个子矩阵的和。p[10][6]的值就是下图中蓝色部分数据的和。

二维前缀和

​ 那如何在o(n^2)的复杂度下求得某个坐标的二维前缀和呢?我们在读入的数组的时候,是逐行从左到右读入,就是说对于每一个位置,他的左边和上面都是已经读入了的或是0 。那么我们可以把(1,1)到(i,j)的矩形分成四块:

二维前缀和

其中阴影最深的是重叠部分。那么,pi,j = (pi-1,j) + (pi,j-1) - (pi-1,j-1)

2、计算子矩阵和:

​ 给出左上端点(i,j)和右下端点(u,v),计算出该矩阵的和。

二维前缀和

根据上图可以发现,sum = (pu,v) - (pi-1,v) - (pu,j-1) + (pi-1,j-1)

要多加一个左上角是因为有重叠部分多减了一次。

///////////////////////////////////////////////////

Codeforces - 1580A - Portal

题目大意:

​ 5行4列的黑曜石框可以做一个传送门,要求是边上用黑曜石填,中间不填,顶点可以填可以不填。给一个n*m二维矩阵表示现在的黑曜石摆放,问改动最小的子矩阵的改动次数是多少。

思路:

​ n最大是400,用二维前缀和去做复杂度是n^4 。我们只要算出每一个子矩阵中间有几个1 ,边上有几个0就可以得到答案。但是运算次数会到2e10,十分危险。所以我们要考虑一下一些情况是没有必要继续进行循环的,比如说我们在扩大子矩阵的右边的过程中发现值已经超过了ans,那继续扩大,值是不可能变小的,所以不用继续循环下去。

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const ll N = 2*1e6+1000;
const ll mod = 1e9 + 7 ;

/////////////////////////////////////////////////

int p[500][500] ;

int get(int i , int j , int u , int v){//计算子矩阵和
	return p[u][v] - p[u][j - 1] - p[i - 1][v] + p[i - 1][j - 1] ;
} 

void solve(){
	
	int n , m , ans = INF ;
	scanf("%d %d", &n , &m ) ;
	getchar() ;
	
	for(int i = 1 ; i <= n ; i ++ , getchar() )
	for(int j = 1 ; j <= m ; j ++){
		char chr = getchar() ;
		p[i][j] = chr - '0' + p[i - 1][j] + p[i][j - 1] - p[i - 1][j - 1] ;	
	}
	
	for(int i = 1 ; i <= n ; i ++ )
	for(int j = 1 ; j <= m ; j ++ )
	for(int u = i + 4 ; u <= n ; u ++ )
	for(int v = j + 3 ; v <= m ; v ++ ){
					
		int ctr = get(i + 1 , j + 1 , u - 1 , v - 1 ) ;
		int  up = v - j - 1 - get(i , j + 1 , i , v - 1 ) ;
		int  dw = v - j - 1 - get(u , j + 1 , u , v - 1 ) ;
		int lft = u - i - 1 - get(i + 1 , j , u - 1 , j ) ;
		int rit = u - i - 1 - get(i + 1 , v , u - 1 , v ) ;
					
		if(ans <= ctr + dw + lft )break ;
					
		ans = min(ans , ctr + up + dw + lft + rit) ;
	
	}
	
	printf("%d\n" , ans );
}

int main(){
	
	ios::sync_with_stdio(false) ;
	//freopen("inn.in","r",stdin) ;
	int T;
	scanf("%d" , &T);
	while(T -- )solve() ;
		
}
上一篇:2.3 文档模式


下一篇:Logisim中六进制计数器的设计