Codeforces Round 662 (Div. 2) #D. Rarity and New Dress (二维dp / bitset卡常)

2024-02-29 12:50

本文主要是介绍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 k1 的小棱形。


在这里插入图片描述
(上图黄色为 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[i1][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][j1]+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=nm+i=1nj=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 k1 小棱形 )

我们设计这样一个 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[k1][i1][j] & \& & d p [ k − 1 ] [ i + 1 ] [ j ] dp[k-1][i+1][j] dp[k1][i+1][j] & \& & d p [ k − 1 ] [ i ] [ j − 1 ] dp[k-1][i][j-1] dp[k1][i][j1] & \& & d p [ k − 1 ] [ i ] [ j + 1 ] dp[k-1][i][j+1] dp[k1][i][j+1]
 
即上下左右都可以构成 k − 1 k-1 k1 阶的菱形时,( 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=1nj=1mk=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 k1 阶中获得 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][i1]&dp[cur][i+1] (当前第 k k k 阶状态 = 第 k − 1 k - 1 k1 阶: 左 & \& & & \& & & \& & 下)
 
(其中 & \& & 代表二进制的与操作, d p [ c u r ] [ i ] dp[cur][i] dp[cur][i] k − 1 k-1 k1 阶第 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卡常)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/758861

相关文章

CSS3 最强二维布局系统之Grid 网格布局

《CSS3最强二维布局系统之Grid网格布局》CS3的Grid网格布局是目前最强的二维布局系统,可以同时对列和行进行处理,将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局,本文介... 深入学习 css3 目前最强大的布局系统 Grid 网格布局Grid 网格布局的基本认识Grid 网

Golan中 new() 、 make() 和简短声明符的区别和使用

《Golan中new()、make()和简短声明符的区别和使用》Go语言中的new()、make()和简短声明符的区别和使用,new()用于分配内存并返回指针,make()用于初始化切片、映射... 详细介绍golang的new() 、 make() 和简短声明符的区别和使用。文章目录 `new()`

poj2576(二维背包)

题意:n个人分成两组,两组人数只差小于1 , 并且体重只差最小 对于人数要求恰好装满,对于体重要求尽量多,一开始没做出来,看了下解题,按照自己的感觉写,然后a了 状态转移方程:dp[i][j] = max(dp[i][j],dp[i-1][j-c[k]]+c[k]);其中i表示人数,j表示背包容量,k表示输入的体重的 代码如下: #include<iostream>#include<

hdu2159(二维背包)

这是我的第一道二维背包题,没想到自己一下子就A了,但是代码写的比较乱,下面的代码是我有重新修改的 状态转移:dp[i][j] = max(dp[i][j], dp[i-1][j-c[z]]+v[z]); 其中dp[i][j]表示,打了i个怪物,消耗j的耐力值,所得到的最大经验值 代码如下: #include<iostream>#include<algorithm>#include<

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

uva 10154 DP 叠乌龟

题意: 给你几只乌龟,每只乌龟有自身的重量和力量。 每只乌龟的力量可以承受自身体重和在其上的几只乌龟的体重和内。 问最多能叠放几只乌龟。 解析: 先将乌龟按力量从小到大排列。 然后dp的时候从前往后叠,状态转移方程: dp[i][j] = dp[i - 1][j];if (dp[i - 1][j - 1] != inf && dp[i - 1][j - 1] <= t[i]

uva 10118 dP

题意: 给4列篮子,每次从某一列开始无放回拿蜡烛放入篮子里,并且篮子最多只能放5支蜡烛,数字代表蜡烛的颜色。 当拿出当前颜色的蜡烛在篮子里存在时,猪脚可以把蜡烛带回家。 问最多拿多少只蜡烛。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cs