一.题面
数圈圈
一句话概括:求字符串矩形中有多少个同一种字符串构成的方框。
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
*/