难度
提 高 + / 省 选 − \color{blue}提高+/省选- 提高+/省选−
个人认为是道状压好题。
题意
给一个二维矩阵。你可以在 P P P 的地方放炮兵,其攻击范围为黑色十字
问你:最多放多少个炮兵,他们相互之间不会攻击到?
数据范围
N ≤ 100 M ≤ 10 N\le 100\\M\le 10 N≤100M≤10
思路
- 我们第一时间联想到一些状压DP的题目(比如互不侵犯)
- 但是这题的十字范围更大,且一行的摆放状态会对两行都产生影响。
- 我们会设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示第 i i i 行放置状态为 j j j ,且上一行放置状态为 k k k 的兵最多数量
- 容易得到一般的转移方程 d p [ i ] [ j ] [ k ] = max p { d p [ i − 1 ] [ k ] [ p ] + c n t j } dp[i][j][k]=\underset{p}{\max}\Big\{dp[i-1][k][p]+cnt_j\Big\} dp[i][j][k]=pmax{ dp[i−1][k][p]+cntj}
- 表示这一行摆放状态为 j j j ,上一行摆放状态为 k k k ,上上行摆放转改为 p p p 的转移。
- c n t j cnt_j cntj 表示这个状态有多少个兵。
- 行合法性要求:每一行的兵都在 P P P 上,每一行相邻的两个兵的距离大于 2 2 2
- 列合法性要求: j & k = 0 j\&k=0 j&k=0 、 j & p = 0 j\&p=0 j&p=0、 k & p = 0 k\&p=0 k&p=0,并且这几行都是合法的。
但是!
- 考虑一下,每一行最多 2 10 = 1024 2^{10}=1024 210=1024 个状态,然后行最多 100 100 100
- 时间复杂度 O ( N ( 2 10 ) 3 ) = O ( 1 e 11 ) O(N(2^{10})^3)=O(1e11) O(N(210)3)=O(1e11)
- 空间复杂度 O ( N ( 2 10 ) 2 ) = O ( 1 e 8 ) O(N(2^{10})^2)=O(1e8) O(N(210)2)=O(1e8)
这不是炸裂?
考虑再度压缩
- 由于每一行的合法性判断是 相邻两个的距离大于2
- 我们使用代码判断为 x & ( x < < 1 ) x\&(x<<1) x&(x<<1) 和 x & ( x < < 2 ) x\&(x<<2) x&(x<<2)
- 但是有没有想过每一行的合法性条件其实是很少的?
- 使用代码跑过之后,易得:每一行十个元素下,最多60种可能的状态
我们使用这些状态去转移,而不是从头算这 2 10 2^{10} 210种状态有哪些合法
- 时间复杂度 O ( N × 6 0 3 ) = O ( 2 e 7 ) O(N\times60^3)=O(2e7) O(N×603)=O(2e7)
- 空间复杂度 O ( N × 6 0 2 ) = O ( 4 e 5 ) O(N\times60^2)=O(4e5) O(N×602)=O(4e5)
空间还能优化
- 考虑状态转移方程,我们第一维每次需要用到的是 d p [ i ] 、 d p [ i − 1 ] 、 d p [ i − 2 ] dp[i]、dp[i-1]、dp[i-2] dp[i]、dp[i−1]、dp[i−2]
- 这样,可以使用滚动数组优化
- 空间复杂度 O ( 3 × 6 0 2 ) = O ( 10800 ) O(3\times60^2)=O(10800) O(3×602)=O(10800)
状压判断:每行地形合法性
- 我们把每一行的 H H H 看做 1 1 1 , P P P 看做 0 0 0
- 这样,每一行的 山脉也被我们状态压缩成hil[x]
- 我们只要判断 j & h i l [ x ] j\&hil[x] j&hil[x] 是否为 0 0 0 即可。
其他一些注意点
- 答案为所有的 d p dp dp 取 max \max max
- 前面两行,与其他行不同,需要单独拎出来。
- 第二行特判的要求是你的总行数大于等于 2 2 2
补充:快速算一个二进制数字有多少位为1
- 注意到
x&(-x)
表示 x x x 的lowbit。 - 我们每次都减去它即可。
ll shu(int x){
ll ans = 0;
while(x){
ans++;
x -= x & (-x);
}
return ans;
}
核心代码
- 时间复杂度 O ( N × 6 0 3 ) = O ( 2 e 7 ) O(N\times60^3)=O(2e7) O(N×603)=O(2e7) 有许多非法性剪枝,不会跑满
- 空间复杂度 O ( 3 × 6 0 2 ) = O ( 10800 ) O(3\times60^2)=O(10800) O(3×602)=O(10800)
T i m e s : 330 ( m s ) M e m o r y : 772 ( K B ) Times:330(ms)\\Memory:772(KB) Times:330(ms)Memory:772(KB)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 2e5+50;
ll dp[5][111][111];
ll cnt[1111];
ll hil[111];
string ss[111];
ll shu(int x){
ll ans = 0;
while(x){
ans++;
x -= x & (-x);
}
return ans;
}
int N,K,ed;
bool check(int x){
if(x & (x << 1))return false;
if(x & (x << 2))return false;
return true;
}
vector<int>V;
int main()
{
cin >> N >> K;
for(int i = 0;i < N;++i){
cin >> ss[i];
int t = 0;
for(int j = 0;j < ss[i].size();++j){
if(ss[i][j] == 'H')t += (1 << j);
}
hil[i] = t;
}
ll ans = 0;
for(int i = 0;i < (1 << K);++i){
if(check(i)){
V.push_back(i),cnt[i] = shu(i);
if(!(i & hil[0]))dp[0][V.size() - 1][0] = cnt[i],ans = max(ans,cnt[i]);
}
}
ed = V.size();
if(N >= 2)
for(int i = 0;i < ed;++i){
int tx = V[i];
if(tx & hil[0])continue;
for(int j = 0;j < ed;++j){
int ty = V[j];
if(ty & hil[1])continue;
if(tx & ty)continue;
dp[1][j][i] = dp[0][i][0] + cnt[ty];
ans = max(ans,dp[1][j][i]);
}
}
for(int i = 2;i < N;++i){
for(int j = 0;j < ed;++j)
for(int k = 0;j < ed;++j)
dp[i%3][j][k] = 0;
for(int j = 0;j < ed;++j){
int tx = V[j];
if(tx & hil[i])continue;
for(int k = 0;k < ed;++k){
int ty = V[k];
if(ty & hil[i-1])continue;
if(tx & ty)continue;
for(int p = 0;p < ed;++p){
int tp = V[p];
if(tp & hil[i-2])continue;
if(tp & tx)continue;
if(tp & ty)continue;
dp[i % 3][j][k] = max(dp[i % 3][j][k],dp[(i-1 +3)%3][k][p] + cnt[tx]);
ans = max(ans,dp[i%3][j][k]);
}
}
}
}
cout << ans;
return 0;
}