原题链接:D. Rarity and New Dress
题目大意:
给出一个 n × m n \times m n×m 带有小写字母的网格,要你找出有多少个菱形(如题中图片所示)满足以下条件:
- 圈起的菱形内部所有字母都必须相同。
- 菱形不能超过网格大小。
- 一个格子也算作一个菱形。
问:对于给出的网格,总共有多少种不同的方法构成一个菱形。
解题思路:
做法 1 1 1 :二维DP
对于一个格子,我们考虑从上下左右四个方向进行转移。
对一个菱形来说,我们假设其上下左右四个方向格子的长度为 k k k 。那么对于单个格子构成的棱形来说 k = 0 k = 0 k=0 ,单个格子加周围四个格子构成的一个小菱形来说 k = 1 k = 1 k=1,以此类推。
观察一下,一个边长为
k
k
k 的菱形能够构成的前提是其四周可以分成四个边长为
k
−
1
k - 1
k−1 的小棱形。
(上图黄色为
k
=
3
k = 3
k=3 阶菱形的中心,深蓝色为
k
=
2
k = 2
k=2 阶小菱形的中心)
那么我们可以先考虑上下方向最大能够扩展的长度,然后再考虑左右方向能够扩展的长度。
- u p [ i ] [ j ] up[i][j] up[i][j] 表示坐标为( i , j i,j i,j )的方格能最大向上扩展的长度。
- d o w n [ i ] [ j ] down[i][j] down[i][j] 表示坐标为( i , j i,j i,j )的方格能最大向下扩展的长度。
- l e f t [ i ] [ j ] left[i][j] left[i][j] 和 r i g h t [ i ] [ j ] right[i][j] right[i][j] 同理。
那么转移方程很好想,几乎都是以下形式:
u
p
[
i
]
[
j
]
=
{
u
p
[
i
−
1
]
[
j
]
+
1
(str[i][j] = str[i - 1][j]),
0
(str[i][j]
≠
str[i - 1][j])
,
up[i][j] = \begin{cases} up[i - 1][j] + 1 \quad \text {(str[i][j] = str[i - 1][j])} ,\\ 0 \quad \quad \quad \quad \quad \quad \quad \text{(str[i][j]$\not=$str[i - 1][j])},\\ \end{cases}
up[i][j]={up[i−1][j]+1(str[i][j] = str[i - 1][j]),0(str[i][j]=str[i - 1][j]),
对于
l
e
f
t
left
left 和
r
i
g
h
t
right
right 来说,我们要额外考虑
u
p
up
up 和
d
o
w
n
down
down 的影响:
l
e
f
t
[
i
]
[
j
]
=
{
min
(
u
p
[
i
]
[
j
]
,
d
o
w
n
[
i
]
[
j
]
,
l
e
f
t
[
i
]
[
j
−
1
]
+
1
)
(str[i][j] = str[i][j - 1]),
0
(str[i][j]
≠
str[i - 1][j])
,
left[i][j] = \begin{cases} \min(up[i][j], down[i][j], left[i][j - 1] + 1) \quad \text {(str[i][j] = str[i][j - 1])} ,\\ 0 \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \text{ (str[i][j]$\not=$str[i - 1][j])},\\ \end{cases}
left[i][j]={min(up[i][j],down[i][j],left[i][j−1]+1)(str[i][j] = str[i][j - 1]),0 (str[i][j]=str[i - 1][j]),
这是因为我们要向左和向右扩展,前提是我们上下能够扩展。最终答案就可以在
l
e
f
t
left
left 和
r
i
g
h
t
right
right 中取得了。
a
n
s
=
n
∗
m
+
∑
i
=
1
n
∑
j
=
1
m
min
(
l
e
f
t
[
i
]
[
j
]
,
r
i
g
h
t
[
i
]
[
j
]
)
。
ans = n * m + \sum_{i =1}^{n}\sum_{j =1}^{m}\min(left[i][j],right[i][j])。
ans=n∗m+∑i=1n∑j=1mmin(left[i][j],right[i][j])。
时间复杂度:
O
(
n
m
)
O(nm)
O(nm)
AC代码:
#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;
using u64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;
void solve() {
int n, m;
cin >> n >> m;
vector<string> str(n + 2);
for (int i = 1; i <= n; ++i) {
cin >> str[i];
str[i] = '?' + str[i] + '?';
}
vector<vector<i64>> up(n + 1, vector<i64>(m + 1, 0));
auto down = up, left = up, right = up;
for (int i = 2; i <= n - 1; ++i) {
for (int j = 2; j <= m - 1; ++j) {
int t = n - i + 1;
if (str[i][j] == str[i - 1][j]) {
up[i][j] = up[i - 1][j] + 1;
}
if (str[t][j] == str[t + 1][j]) {
down[t][j] = down[t + 1][j] + 1;
}
}
}
for (int i = 2; i <= n - 1; ++i) {
for (int j = 2; j <= m - 1; ++j) {
int t = m - j + 1;
if (str[i][j] == str[i][j - 1]) {
left[i][j] = min({up[i][j], down[i][j], left[i][j - 1] + 1});
}
if (str[i][t] == str[i][t + 1]) {
right[i][t] = min({up[i][t], down[i][t], right[i][t + 1] + 1});
}
}
}
i64 ans = n * m;
for (int i = 2; i <= n - 1; ++i) {
for (int j = 2; j <= m - 1; ++j) {
ans += min(left[i][j], right[i][j]);
}
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1; //cin >> t;
while (t--) solve();
return 0;
}
做法 2 2 2 : bitset卡常
假设我们进行最暴力的做法,利用做法 1 1 1 的思路:
( 一个边长为 k k k 的菱形能够构成的前提是其四周可以分成四个 k − 1 k - 1 k−1 小棱形 )
我们设计这样一个 0 / 1 0/1 0/1 状态: d p [ k ] [ i ] [ j ] dp[k][i][j] dp[k][i][j] 表示坐标为( i , j i,j i,j)的点能否构成长度为 k k k 的菱形,为 1 1 1 则可以构成,为 0 0 0 则不能构成。
那么状态转移方程就是:
d
p
[
k
]
[
i
]
[
j
]
=
d
p
[
k
−
1
]
[
i
−
1
]
[
j
]
dp[k][i][j] = dp[k-1][i -1][j]
dp[k][i][j]=dp[k−1][i−1][j]
&
\&
&
d
p
[
k
−
1
]
[
i
+
1
]
[
j
]
dp[k-1][i+1][j]
dp[k−1][i+1][j]
&
\&
&
d
p
[
k
−
1
]
[
i
]
[
j
−
1
]
dp[k-1][i][j-1]
dp[k−1][i][j−1]
&
\&
&
d
p
[
k
−
1
]
[
i
]
[
j
+
1
]
dp[k-1][i][j+1]
dp[k−1][i][j+1]
即上下左右都可以构成
k
−
1
k-1
k−1 阶的菱形时,(
i
,
j
i,j
i,j)的点就可以构成
k
k
k 阶的菱形。
最后,对于每一个
k
k
k ,我们算出一共有多少个
1
1
1 即可。
a
n
s
=
∑
i
=
1
n
∑
j
=
1
m
∑
k
=
1
min
(
n
,
m
)
2
d
p
[
k
]
[
i
]
[
j
]
ans = \sum_{i =1}^{n}\sum_{j =1}^{m}\sum_{k=1}^{{\min(n,m) \over 2}}dp[k][i][j]
ans=∑i=1n∑j=1m∑k=12min(n,m)dp[k][i][j]
O ( n 3 ) O(n^{3}) O(n3) 的复杂度是过不了的,但我们发现 d p dp dp 数组中都是对 0 / 1 0/1 0/1 进行维护,考虑用 b i t s e t bitset bitset 优化。
开 b i t s e t bitset bitset 来代替我们的 d p dp dp 数组,然后利用类似滚动数组的方法从 k − 1 k - 1 k−1 阶中获得 k k k 阶的状态。
先处理出 k = 1 k=1 k=1 时的 d p dp dp 数组 0 / 1 0/1 0/1 状态存入一个 b i t s e t bitset bitset 内。
预处理之后,那么后面
2
,
3
,
.
.
.
,
k
2,3,...,k
2,3,...,k 阶的状态转移方程为:
d
p
[
n
x
t
]
[
i
]
=
(
d
p
[
c
u
r
]
[
i
]
<
<
1
)
&
(
d
p
[
c
u
r
]
[
i
]
>
>
1
)
&
d
p
[
c
u
r
]
[
i
−
1
]
&
d
p
[
c
u
r
]
[
i
+
1
]
dp[nxt][i] = (dp[cur][i] << 1) \& (dp[cur][i] >> 1) \& dp[cur][i - 1] \& dp[cur][i + 1]
dp[nxt][i]=(dp[cur][i]<<1)&(dp[cur][i]>>1)&dp[cur][i−1]&dp[cur][i+1] (当前第
k
k
k 阶状态 = 第
k
−
1
k - 1
k−1 阶: 左
&
\&
& 右
&
\&
& 上
&
\&
& 下)
(其中
&
\&
& 代表二进制的与操作,
d
p
[
c
u
r
]
[
i
]
dp[cur][i]
dp[cur][i] 是
k
−
1
k-1
k−1 阶第
i
i
i 行的
d
p
dp
dp 状态,
d
p
[
n
x
t
]
[
i
]
dp[nxt][i]
dp[nxt][i] 是
k
k
k 阶第
i
i
i 行的
d
p
dp
dp 状态)
做完这些之后,剩下的就是看测评姬和你是否心灵相通了。
(当然也可以用循环展开等等一些魔幻的方法成为卡常超人,不过我懒就是了)
复杂度:
O
(
n
3
/
w
)
O(n^{3} / w)
O(n3/w)
注意一些卡常细节还是能卡过去的。
AC代码:
#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;
using u64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;
const int N = 2e3;
bitset<N> dp[2][N];
void solve() {
int n, m;
cin >> n >> m;
vector<string> str(n);
for (auto& s : str) {
cin >> s;
}
i64 ans = n * m;
//处理出 k = 1 阶的 bitset
for (int i = 1; i < n - 1; ++i) {
for (int j = 1; j < m - 1; ++j) {
char cur = str[i][j];
dp[0][i][j] = (cur == str[i + 1][j] && cur == str[i - 1][j] && cur == str[i][j + 1] && cur == str[i][j - 1]);
}
ans += dp[0][i].count();
}
//用res小小优化一下转移次数
i64 res = 1;
int cur = 0, nxt = 1, t = min(n, m) >> 1;
for (int k = 1; res && k <= t; ++k) {
res = 0;
for (int i = 1; i < n - 1; ++i) {
dp[nxt][i] = (dp[cur][i] << 1) & (dp[cur][i] >> 1) & dp[cur][i - 1] & dp[cur][i + 1];
res += dp[nxt][i].count();
}
ans += res;
swap(cur, nxt);
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1; //cin >> t;
while (t--) solve();
return 0;
}