矩形分治好题

一.题面

数圈圈
一句话概括:求字符串矩形中有多少个同一种字符串构成的方框。 n , m < = 2000 n, m<=2000 n,m<=2000 且都是小写字母。

二.分析:

             首先, O ( n 3 ) O(n^3) O(n3) 的复杂度肯定是过不去的,我们考虑更优秀的做法。

            我们考虑对矩形分治,有以下图示:
在这里插入图片描述
            红色的线将所有的方框分成了3类: 在红线左侧区域的,在红线右侧区域的,以及经过红线的(边界落在红线也算)。
            我们可以每次计算经过红线的方框数量,再分别递归去算左右小矩形内的方框数量即可。

            红线需要是矩形的中线且长度等于较短一边的边长。这样才能保证时间复杂度

             考虑如何计算经过中线的方框。对于红线上的两点 u u u v v v。我们计算红线左边"匚"的数量,和右侧对称的数量,然后乘一下就是 u u u v v v 的贡献。设 L [ u ] L[u] L[u] 表示 u u u 向左,最多能延伸到第几列, D x , y D_{x,y} Dx,y 表示 ( x , y ) (x,y) (x,y) 往下最多能延伸到第几行。那么对于 u , v u, v u,v 而言,左边"匚"的数量为:

             ∑ i = m a x ( L [ u ] , L [ v ] ) m i d [ D u , i > = v ] \sum_{i=max(L[u],L[v])}^{mid}[D_{u, i} >= v] i=max(L[u],L[v])mid[Du,i>=v]


            如果 L [ u ] > = L [ v ] L[u] >=L[v] L[u]>=L[v],就是求 ∑ i = L [ u ] m i d [ D u , i > = v ] \sum_{i=L[u]}^{mid}[D_{u, i} >= v] i=L[u]mid[Du,i>=v]

            我们可以开一个 存下所有 D u , i D_{u, i} Du,i,然后对于每一个符合条件的 v v v ,答案即是一个后缀和即可。

            否则,我们可以求一个 向上延伸 u p up up 数组,然后求 存 u p v , i up_{v, i} upv,i 的桶的前缀和即可。复杂度都是 O ( 1 ) O(1) O(1)


两个要点
1.关于如何调转矩形: 我们可以将原矩形按照调转后的方式再存储一遍,调转方式为 横纵坐标交换。每次递归求解时的参数:

void cut(int T, int lx, int ly, int rx, int ry){
...
}

其中, T T T 代表是原矩形还是翻转后的矩形, ( l x , l y ) , ( r x , r y ) (lx, ly),(rx,ry) (lx,ly)(rx,ry) 分别为左上角和右下角的坐标,每次在当前矩形里计算就可以了,如果需要调转,则将 T T T 异或 1,并且把小矩形的两个顶点的横纵坐标分别交换即可。

2.关于时间复杂度:
            我们考虑最多会划分 l o g 2 m n log_2mn log2mn 层(面积每次除以2),而在每一层中,总复杂度是计算若干个小矩形的复杂度之和。设当前层中的小矩形较短的边长为 x x x,较长的边长为 y y y,那么计算一个小矩形的时间复杂度为 O ( c 1 x 2 + c 2 x y ) < O ( c m a x ( c 1 , c 2 ) x y ) O(c_1x^2 + c_2xy) <O(c_{max(c1,c2)}xy) O(c1x2+c2xy)<O(cmax(c1,c2)xy)因为 x y xy xy 是小矩形的面积,而常数 c 1 , c 2 c1, c2 c1,c2 忽略不计,所以计算每一层中所有小矩形的复杂度为 m n mn mn,总复杂度为 O ( m n l o g 2 m n ) O(mnlog_2mn) O(mnlog2mn)

            这也就解释了为什么每次分割要沿着较短的边,如果每次都沿着较长的边切割,那么每个小矩形的复杂度是 O ( c 1 y 2 + c 2 x y ) O(c_1y^2 +c_2xy) O(c1y2+c2xy),而 y y y 是等于 n n n 的,每一层最坏复杂度为 O ( n 3 ) O(n^3) O(n3),因此总复杂度是趋近 O ( n 3 l o g n m ) O(n^3lognm) O(n3lognm)。 显然不可过掉本题。

最后放上代码:

#include<bits/stdc++.h>//对矩形分治, 每次沿着短边切 
using namespace std;
typedef long long LL;
const int N = 2100;
int n, m;
char mp[2][N][N];
LL res, l[N], down[N][N], sumd[N][N], up[N][N], sumu[N][N], r[N], num_l[N][N], num_r[N][N];
void Clear(int lx, int ly, int rx, int ry, int mid){
	for(int i = lx; i <= rx; i++){
		for(int j = lx; j <= rx; j++){
			sumd[i][j] = 0;
			sumu[i][j] = 0;
		}
		l[i] = r[i] = mid;
	} 	
}
void cut(int T, int lx, int ly, int rx, int ry){//左上角(lx, ly), 右下角(rx, ry) 
	if(rx <= lx && ry <= ly) return ;//切完了
	int mid = (ly + ry >> 1);//中间
    Clear(lx, ly, rx, ry, mid);//清空
    //求左边框 
	for(int i = lx; i <= rx; i++){//求向4左延伸的长度 
		for(int j = mid - 1; j >= ly; j--){
			if(mp[T][i][j] == mp[T][i][mid]) l[i] = j;
			else break;
		}
	}

	for(int i = ly; i <= ry; i++){//求向下延伸的长度 和 向上延伸的长度 
		for(int j = rx; j >= lx; j--){
			if(j == rx) down[j][i] = j;
			else{
				if(mp[T][j][i] == mp[T][j + 1][i]) down[j][i] = down[j + 1][i];
				else down[j][i] = j;
			}
		}
		for(int j = lx; j <= rx; j++){
			if(j == lx) up[j][i] = j;
			else{
				if(mp[T][j][i] == mp[T][j - 1][i]) up[j][i] = up[j - 1][i];
				else up[j][i] = j;
			}
		}
	}           
	
	for(int i = lx; i <= rx; i++){
		for(int j = mid - 1; j >= l[i]; j--){
			sumd[i][down[i][j]]++;
			sumu[i][up[i][j]]++;
		}
		for(int j = rx - 1; j >= lx; j--){//求后缀 
			sumd[i][j] = sumd[i][j + 1] + sumd[i][j];
		}
		for(int j = lx + 1; j <= rx; j++){//求前缀 
			sumu[i][j] = sumu[i][j - 1] + sumu[i][j];
		}
	}
	
	for(int i = lx; i <= rx; i++){
		for(int j = i + 1; j <= rx; j++){//固定上侧  枚举下侧 
		    if(mp[T][i][mid] == mp[T][j][mid]){	
				if(l[j] <= l[i])//包含 
					num_l[i][j] = sumd[i][j];//大于等与j的 
				else num_l[i][j] = sumu[j][i];//小于等于i的 
			}
			else num_l[i][j] = 0;
		}
	}
	//求有边框 
	Clear(lx, ly, rx, ry, mid);

	for(int i = lx; i <= rx; i++){//求向右延伸的长度 
		for(int j = mid + 1; j <= ry; j++){
			if(mp[T][i][j] == mp[T][i][mid]) r[i] = j;
			else break;
		}
	}         

	for(int i = lx; i <= rx; i++){
		for(int j = mid + 1; j <= r[i]; j++){
			sumd[i][down[i][j]]++;
			sumu[i][up[i][j]]++;
		}
		for(int j = rx - 1; j >= lx; j--){//求后缀
			sumd[i][j] = sumd[i][j + 1] + sumd[i][j];
		}
		for(int j = lx + 1; j <= rx; j++){//求前缀 
			sumu[i][j] = sumu[i][j - 1] + sumu[i][j];
		}
	}
	
	for(int i = lx; i <= rx; i++){
		for(int j = i + 1; j <= rx; j++){//固定上侧  枚举下侧 
		    if(mp[T][i][mid] == mp[T][j][mid]){
				if(r[j] >= r[i])//包含 
					num_r[i][j] = sumd[i][j];//大于等与j的 
				else num_r[i][j] = sumu[j][i];//小于等于i的 
			}
			else num_r[i][j] = 0;
		}
	}	
	
	for(int i = lx; i <= rx; i++){
		for(int j = i + 1; j <= rx; j++){
			res += num_l[i][j] * num_r[i][j];
			if(down[i][mid] >= j) res = res + num_l[i][j] + num_r[i][j];
		}
	}

	if(mid - ly < rx - lx + 1) cut(T ^ 1, ly, lx, mid - 1, rx);
	else cut(T, lx, ly, rx, mid - 1);
	if(ry - mid < rx - lx + 1) cut(T ^ 1, mid + 1, lx, ry, rx);
	else cut(T, lx, mid + 1, rx, ry);
}
int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			cin >> mp[0][i][j];
			mp[1][j][i] = mp[0][i][j];
		}
	}
	if(n >= m) cut(1, 1, 1, m, n);//竖着切 
	else cut(0, 1, 1, n, m);
	cout << res << endl;
	return 0;
}
/*
3 5
bbbbb
baabb
baabb
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值