[日常训练] CZA的蛋糕

【问题描述】

cza特别喜欢吃海苔,怎么吃也吃不够。cza的生日到来时,他的父母给他买了许许多多的海苔和一个生日蛋糕。海苔是一个1 × 2或2 × 1的长方形,而蛋糕则是一个n × m的矩阵。蛋糕上有一些蜡烛占据了位置,其他地方都可以放海苔。cza的父母让cza把海苔尽可能多的放在蛋糕上,但是海苔不能够重叠放置。cza想把海苔留着自己以后慢慢吃,可又不敢违背父母,于是他决定放一少部分在蛋糕上。为了不使父母起疑,cza必须确保放置完海苔后,蛋糕上不存在1 × 2和2 × 1的空白以放置更多的海苔。cza想知道这样得花多少海苔,请帮助他求出满足这样放置所需的最少海苔数。

【输入格式】

输入的第一行是蛋糕的规模n和m(注意是n行m列)
接下来的n行每一行含m个字符。每个字符要么是”.”,表示空白;要么是”*”,表示蜡烛

【输出格式】

输出文件只包含一个整数k,表示满足题目要求的最小海苔数。

【输入样例】

3 3

.*.

【输出样例】

3

【数据范围与约定】

对于30%的数据N<=5,M<=5。
对于100%的数据N<=70, M<=7。

【分析】状压DP + 搜索

  • 看到数据范围 M7 ,很容易想到用一个7位二进制数表示某一行的每个位置上是否有海苔
  • 因为在某一行上放海苔可能会影响到下一行的状态(在这一行放 2×1 的海苔),所以我们记 f[i][stai][stai+1] ,表示选取到第 i 行,第i行的状态为 stai ,第 i+1 行的状态为 stai+1 所放的最小海苔数
  • 我们枚举上一次的状态来转移,对于这一次可能出现的状态及其所需放的海苔数,直接暴搜出来转移即可
  • 那么 DP 初值为 f[0][2m1][s1]=0 s1 表示第一行的蜡烛摆放状态,因为蜡烛所在位置是不能放的),这里我们假定存在第 0 行且其已经放满,因为在第1行某个位置不放其实是没有关系的
  • 而最后的答案 Answer=Min(f[n][stan][0]) ,因为假定的第 n+1 行我们是不能放的

【代码】

#include <iostream>
#include <cstdio>

using namespace std;
const int Maxn = 0x3f3f3f3f;
const int N = 75, M = (1 << 7) + 5;
int f[2][M][M], a[N]; char c[10];
int i, j, k, n, m, Ans = Maxn;

inline void CkMin(int &x, const int &y) {if (x > y) x = y;}

inline void Dfs(const int &x, const int &opx, const int &opy, const int &opz, const int &stp)
//opx表示当前第i - 1行的海苔状态,opy表示当前第i行的海苔状态,opz表示当前第i + 1行的海苔状态 
{
    if (x > 0 && (opx & (1 << x - 1)) == 0 && (opy & (1 << x - 1)) == 0) return ;
    if (x > 1 && (opy & (1 << x - 1)) == 0 && (opy & (1 << x - 2)) == 0) return ;
    if (x == m) return CkMin(f[i & 1][opy][opz], f[i - 1 & 1][j][k] + stp);
    if ((opy & (1 << x)) == 0 && (opz & (1 << x)) == 0)
     Dfs(x + 1, opx, opy | (1 << x), opz | (1 << x), stp + 1);
    if (x < m - 1 && (opy & (1 << x)) == 0 && (opy & (1 << x + 1)) == 0)
     Dfs(x + 2, opx, opy | (1 << x) | (1 << x + 1), opz, stp + 1); 
     Dfs(x + 1, opx, opy, opz, stp);
}

int main() 
{
    freopen("cake.in", "r", stdin);
    freopen("cake.out", "w", stdout); 
    scanf("%d%d", &n, &m);
    for (i = 1; i <= n; ++i)
    {
        scanf("%s", c + 1);
        for (int j = 1; j <= m; ++j)
         if (c[j] == '*') a[i] |= (1 << j - 1); 
    }
    int T = (1 << m);
    for (j = 0; j < T; ++j)
     for (k = 0; k < T; ++k) f[0][j][k] = Maxn;
    f[0][T - 1][a[1]] = 0;
    for (i = 1; i <= n; ++i)
    {
        for (j = 0; j < T; ++j)
         for (k = 0; k < T; ++k) f[i & 1][j][k] = Maxn;
        for (j = 0; j < T; ++j)
         for (k = 0; k < T; ++k)
          if (f[i - 1 & 1][j][k] < Maxn)
           Dfs(0, j, k, a[i + 1], 0); 
    }
    for (j = 0; j < T; ++j) CkMin(Ans, f[n & 1][j][0]);
    printf("%d\n", Ans);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值