算法课设:完全覆盖(基于鸿蒙+TS实现,编译软件DevEco Studio,图解详细,附核心代码)

1 篇文章 0 订阅
1 篇文章 0 订阅

1、算法描述

        有一天acmj在玩一种游戏----用2*1或1*2的骨牌把m*n的棋盘完全覆盖。但他感觉把棋盘完全覆盖有点简单,他想能不能把完全覆盖的种数求出来?由于游戏难度增加他自己已经没法解决了,于是他希望大家能用程序来帮他把问题解决了。

输入

有多组数据。

每组数据占一行,有两个正整数n(0<n<12),m(0<m<12)。

当n,m等于0时输入结束

输出

每组数据输出占一行,输出完全覆盖的种数。

样例输入

2 2

2 3

2 4

2 11

4 11

0 0

样例输出2

3

5

144

51205

2、项目展示

2.1、加载界面

        第一界面:主要是运用到了插入图片,显示文字,设置背景,使用计时器,还有一个动态效果的“正在加载”图案,用主要包含row,column的线性布局结构将它们穿插在一起,再设置一些字体的颜色,大小,格式,外边距等让整体界面美观。整体效果如下图所示:

2.2、题目描述

        第二界面:主要是显示题目的要求,包括对输入,输出的要求,便于使用者了解题目大概和输入,输出格式,其他基本上就是显示text文本框所包含内容并对其格式进行设置,在该界面下方设置了一个按钮,用于跳转到下一界面;整体效果如下图所示:

2.3、输入页面

        第三界面:首先是定义了文本框,显示输入输出样例,让使用者在输入数据时有一个参考,明白输入格式,接下来设置了一个输入文本框TextArea,等待数据传入,在输入合法数据之后点击下方按钮“计算”,会在本页面内的算法代码实现过程传入数据并得出对应的结果,再将必要的输入和输出数据传到下一界面中,同时跳转到下一界面;整体效果如下图所示:

2.4、结果显示

        第四界面:当第三界面的数据输入并点击“计算”按钮之后,输入的各组n,m和对应结果都被保存在一个params对象中被传入第四界面,通过对线性结构的运用再将它们分别显示出来。整体效果如下图所示:

3、算法分析

3.1、题意分析

        给定n*m的方格,看能分成多少个1 *2的小方块。

3.2、题意解读

        摆放方块的时候,先放横着的,再放竖着的。总方案数等于只放横着的小方块的合法方案数。

        如何判断,当前方案数是否合法? 所有剩余位置能否填充满竖着的小方块。可以按列来看,每一列内部所有连续的空着的小方块需要是偶数个。

        这是一道动态规划的题目,并且是一道 状态压缩的dp:用一个N位的二进制数,每一位表示一个物品,0/1表示不同的状态。因此可以用0 → 2 N − 1 ( N 二 进 制 对 应 的 十 进 制 数 ) 0\rightarrow 2^N-1(N二进制对应的十进制数)0→2 N −1(N二进制对应的十进制数)中的所有数来枚举全部的状态。

        算法流程图如下所示:

3.3、状态表示

        状态表示:f[i][j]表示已经将前 i -1 列摆好,且从第i−1列,伸出到第 i列的状态是 j的所有方案。其中j是一个二进制数,用来表示哪一行的小方块是横着放的,其位数和棋盘的行数一致。 请看下面的释义。

        解释:

        首先进行预处理1:对于每种状态,先预处理每列不能有奇数个连续的0。将预处理1的结果都存入st[i]数组中。

        然后进行预处理2:看第i-2列伸出来的和第i-1列伸出去的是否冲突。

        预处理1和预处理2对于结果的推导如图所示。

3.4、状态转移

        若是预处理1和2都可以通过,则可以将该方案转移,所有当前列的方案数都等于之前的第i-1列所有状态k的累加,最终可以计算出所求方案数f[m][0]。

4、项目介绍

        项目共分为四个界面:加载界面、题目描述、输入处理、结果显示。这里仅展示我认为较为关键的部分。

4.1、加载页面

        图片设置,将项目内的图片加载到区域背景中。(该部分代码仅供参考)

Column(){

      }
      .margin({  //设置外边距属性
        top:50
      })
      .width('90%')  //设置这一块地方的宽,高,背景图片和背景图片属性
      .height(200)
      .border({ width: 1 })
      .backgroundImage($r('app.media.pan1'))
      .backgroundImageSize({ width: '1200px', height: '600px' })

        设置两秒的定时器,时间到后跳转第二个界面。(该部分代码仅供参考)

aboutToAppear() {
    console.info('IntPage 界面打开');
    this.startTimer()
  }
  startTimer() {
    // 设置两秒后的定时器
    setTimeout(() => {
      // 两秒后执行页面跳转
      router.pushUrl({url: 'pages/QuestionRequirementsPage'}).then(() => {
        console.info('成功跳转到 QuestionRequirementsPage');
      }).catch((err) => {
        console.error(`跳转界面失败. Code is ${err.code}, message is ${err.message}`);
      });
    }, 2000); // 2秒
  }

        设置加载动画,强化动态效果,使用的是官方文档提供的一个动画效果。(该部分代码仅供参考)

LoadingProgress()
          .color(Color.Blue)
          .width(100)
          .height(100)

4.2、题目描述

        这个页面就是简单的UI设计,将文本显示在页面上,方便理解题意。

        在这里贴出按钮实现页面跳转功能代码。(该部分代码仅供参考)

import router from '@ohos.router'  // 在最上面导入api


Button('输入数据', { type: ButtonType.Normal })
            .borderRadius(20)
            .width(200)
            .height(60)
            .margin({
              top:40
            })
            .onClick(()=>{
              router.pushUrl({url: 'pages/MainPage'}).then(() => {
                console.info('成功跳转到 MainPage');
              }).catch((err) => {
                console.error(`跳转界面失败. Code is ${err.code}, message is ${err.message}`);
              });
            })

4.3、输入页面

        检测到错误格式后,显示弹窗提示。(该部分代码仅供参考)

AlertDialog.show(
            {
              title: '错误',
              message: '    输入的格式有误,请重新输入',
              autoCancel: true,
              offset: { dx: 0, dy: -10 },
              alignment: DialogAlignment.Center,
              gridCount: 3,
              confirm: {
                value: '确认',
                fontColor:Color.White,
                backgroundColor:Color.Red,
                action: () => {
                  console.info('Button-clicking callback')
                }
              }
            }
          )

        将这个页面算出的结果,传输到结果页面,仍然有跳转页面,记得导入api。(该部分代码仅供参考)

router.pushUrl({
            url: 'pages/SituationsPage', // 目标页面的URI或路由名
            params: {
              n: numbers_n,
              m:numbers_m,
              results: result_arr,
            }
          }).then(() => {
            console.info('成功跳转到 SituationsPage');
          }).catch((err) => {
            console.error(`跳转界面失败. Code is ${err.code}, message is ${err.message}`);
          });

4.4、结果显示

        按照上个界面算出的结果,将多个结果显示。(该部分代码仅供参考)

ForEach(this.results, (item,index) => {
          Column(){
            Row() {
              Column() {
                Text('n:' + this.n[index])
                  .textAlign(TextAlign.Center)
                  .fontSize(35)
                  .width('50%')
              }

              Column() {
                Text('m:' + this.m[index])
                  .textAlign(TextAlign.Center)
                  .fontSize(35)
                  .width('50%')
              }
            }
            .margin({
              top:20
            })
            Row() {
              Text('result:' + item.toString())
                .fontSize(35)
                .textAlign(TextAlign.Center)
                .width('100%')
              }
            .margin({
              top:20
            })
          }
          .width('90%')
          .height(150)
          .backgroundColor(this.Color)
          .borderRadius(15)
          .margin({ top: 10 })
        }, item => item)

5、核心代码

        该项目的核心部分在于输入数据处理以及算法实现部分,我会在下面将代码贴出(代码由Ts语言实现)。

5.1、输入数据处理

function processStringToNumbers(inputString: string): number[]

{

      const pairs = inputString.split('\n').join(' ').split(' ');

      const numbers: number[] = [];

      for (let i = 0; i < pairs.length; i += 2) {

        // 检查是否到达'0 0'结束标记

        if (pairs[i] === '0' && pairs[i + 1] === '0') {

          break;       }

    // 注意:这里假设每对数字都是有效的,并且为整数

       numbers.push(parseInt(pairs[i], 10));  

       numbers.push(parseInt(pairs[i + 1], 10));

     }

      return numbers;

}

5.2、完全覆盖算法函数

function completeCoverage(n:number,m:number): number
{
  // const M:number = 1<<n
  // f的大小应该是 f[m][2^n],共有m列,每一列有2^n状态
  const f = new Array(m+1).fill(0).map(m=>new Array(1<<n).fill(0))  //二维数组 f,使用 fill(0) 方法填充初始值为 0,然后通过 map 方法对每一行进行处理,将每一行都填充为长度为 1<<n 的数组,并初始化值为 0
  const st = new Array(1<<n).fill(false) //状态数组,长度为 1<<n 的布尔数组 st,元素初始值均为 false
  // 枚举n位二进制数(因为状态是按列压缩的,总共二进制位有n个)
  for(let i = 0; i< 1<<n; i++){
    // 枚举每一位
    let isValid = true
    let cnt = 0
    for(let j = 0; j<n; j++){
      // 如果此时第j位是1
      if(i >> j & 1){
        // 那么需要判断当前的cnt数量是不是奇数
        if(cnt & 1){    //即cnt确实为奇数
          isValid = false
          break;
        }
        cnt = 0
      }else{
        cnt++
      }
    }
    if(cnt & 1){
      isValid = false
    }
    st[i] = isValid
  }

  // 将st数组再次预过滤,成为新的state表
  const state = {}
  for(let i = 0; i< 1<<n; i++){
    state[i] = []
    for(let j = 0; j< 1<<n; j++){
      if((i&j)===0 && st[i|j]){
        state[i].push(j)
      }
    }
  }

  // 最后再进行dp
  f[0][0] = 1 //先初始化,表示第0列没有被伸到的摆放方式,即空棋盘,有1种方式
  // 先枚举所有列
  for(let i = 1; i<=m; i++){
    // 再枚举每一列里面的所有排列组合
    for(let j = 0; j< 1<<n; j++){
      // 如果state表里有这个key,说明j是一个合法的状态,则可以转移
      for(let k of state[j]){
        f[i][j] += f[i-1][k]
      }
    }
  }
  return f[m][0]
}

参考文献

Acwing291. 蒙德里安的梦想:状态压缩dp

华为官方文档

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值