#include <bits/stdc++.h> #define debug(x) std::cerr << "Line: " << __LINE__ << \ "," << #x << "=" << x << "\n" using ll = long long; constexpr int N = 105; int a[N]; // dp[上上行01状态][上行01状态][滚动] int dp[1 << 10][1 << 10][3], sum[1 << 10]; int main(){ std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int n, m; std::cin >> n >> m; char x; for(int i = 0; i < n; i++){ for(int j = 0; j < m; j++){ std::cin >> x; if(x == 'H') a[i] |= (1 << j);//如果是山,那一位就是1 } } for(int i = 0; i < (1 << m); i++){ sum[i] = __builtin_popcount(i);//提前计算好对于每一个十进制,1的个数 } //枚举第一行的每一种摆放状态 for(int S = 0; S < (1 << m); S++){ /* 同时满足三个条件: 1. 刚好和第一行地形"交错"放置 2. 每个位置的前一格没有炮 3. 每个位置的前两格没有炮 */ if(!(S & a[0] || S & (S << 1) || S & (S << 2) )){ dp[0][S][0] = sum[S];//第0行什么都没有,第一行是枚举的初始化状态,滚动:0, 所放置的炮个数 } } //枚举第一、二行的每一种摆放状态 for(int L = 0; L < (1 << m); L++){//枚举上排状态 O(2 ^ m) for(int S = 0; S < (1 << m); S++){//枚举这排状态 O(2 ^ m) /* 转移满足的条件: 1.满足上行和这行不冲突 2.满足上行和第一行不冲突 3.满足这行和第二行不冲突 4.满足上行与自己前一格、两格不冲突 5.满足这行与自己前一格、两格不冲突 */ if(!(L & S || L & a[0] || S & a[1] || (L & (L<<1)) || (L & (L<<2)) || (S & (S<<1)) || (S & (S<<2)))) { //谜之一长串特判 dp[L][S][1] = sum[S] + sum[L];//将前两行的所放置炮的个数加起来 } } } //初始化完成,可以借以上两个初始化数据向后滚动递推 for(int i = 2; i < n; i++){//对于之后的行 for(int L = 0; L < (1 << m); L++){//这一行的上上行 /* 特判 1.上行摆放状态与上行地形冲突 2.自己de摆放状态和自己冲突 */ if(L & a[i - 1] || L & (L << 1) || L & (L << 2)){ continue; } for(int S = 0; S < (1 << m); S++){ /* 特判 1.这行摆放状态与这行地形冲突 2.自己de摆放状态和自己冲突 */ if(S & a[i] || L & S || S & (S << 1) || S & (S << 2)){ continue; } for(int FL = 0; FL < (1 << m); FL++){ /* 特判 1.上上行摆放状态与上上行地形冲突 2.上上自己de摆放状态和自己冲突 */ if(FL & L || FL & S || FL & a[i - 2] || FL & (FL << 1) || FL & (FL << 2)){ continue; } //滚动更新,这个状态要么和原来的一样,要么转移 dp[L][S][i % 3] = std::max(dp[L][S][i % 3], dp[FL][L][(i - 1) % 3] + sum[S]); } } } } int ans = -1e9; for(int L = 0;L < (1 << m);L ++) for(int S = 0; S < (1 << m); S++) ans = std::max(ans,dp[L][S][(n - 1) % 3]); //结束状态可以是最后一行的任何状态 std::cout << ans; return 0; }