本文主要是介绍Codeforces Round 662 (Div. 2) #D. Rarity and New Dress (二维dp / bitset卡常),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原题链接: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 阶的 bitsetfor (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;
}
感谢观看!
这篇关于Codeforces Round 662 (Div. 2) #D. Rarity and New Dress (二维dp / bitset卡常)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!