c++算法基础必刷题目——递推

递推

  递推算法是一种简单的算法,即通过已知条件,利用特定关系得出中间推论,直至得到结果的算法。递推算法分为顺推和逆推两种。

1、扫雷MINE

NC20241 扫雷MINE

题目描述

  相信大家都玩过扫雷的游戏。那是在一个n*m的矩阵里面有一些雷,要你根据一些信息找出雷来。
  万圣节到了 ,“余”人国流行起了一种简单的扫雷游戏,这个游戏规则和扫雷一样,如果某个格子没有雷,那么它里面的数字 表示和它8连通的格子里面雷的数目。
  现在棋盘是n×2的,第一列里面某些格子是雷,而第二列没有雷,如下图: 由于第一列的雷可能有多种方案满足第二列的数的限制,你的任务即根据第二列的信息确定第一列雷有多少种摆放方案。

输入描述:
第一行为N,第二行有N个数,依次为第二列的格子中的数。(1 ≤ N ≤ 10000)

输出描述:
一个数,即第一列中雷的摆放方案数。

示例1
输入
2
1 1

输出
2

方法一 动态规划

扫描二维码关注公众号,回复: 14741310 查看本文章

解题思路:
1、用二进制代表附近三位的状态1代表有雷,0代表没有,那么状态一共有8个,000、001、010、011、100、101、110、111,下面解释样例

2、初始地,a[2]={1,1},i=0时,a[i]=1,那么状态dp[0][001]=1,dp[0][010]=1,i=1时,a[i]=1,那么状态dp[1][010]+=dp[0][001],dp[1][100]+=dp[0][010],仔细分析转移的关系即可,例如100可以由010以及110转移得到,011可以由001以及101转移得到,即前两位与前一个状态的后两位相等

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    
    
    int n;
    int a[10010]={
    
    0};
    int dp[10010][8]={
    
    0};
    cin>>n;
    int ans=0;
    for(int i=1;i<=n;i++){
    
    
        cin>>a[i];
        if(i==1){
    
    //第一个需要初始化
            if(a[i]==0){
    
    
                dp[i][0]++;
            }else if(a[i]==1){
    
    
                dp[i][1]++;
                dp[i][2]++;
            }else if(a[i]==2){
    
    
                dp[i][3]++;
            }
        }else{
    
    //状态转移
            if(a[i]==0){
    
    
                dp[i][0]+=dp[i-1][0]+dp[i-1][4];
            }else if(a[i]==1){
    
    
                dp[i][1]+=dp[i-1][0]+dp[i-1][4];
                dp[i][2]+=dp[i-1][1]+dp[i-1][5];
                dp[i][4]+=dp[i-1][2]+dp[i-1][6];
            }else if(a[i]==2){
    
    
                dp[i][3]+=dp[i-1][1]+dp[i-1][5];
                dp[i][5]+=dp[i-1][2]+dp[i-1][6];
                dp[i][6]+=dp[i-1][3]+dp[i-1][7];
            }else if(a[i]==3){
    
    
                dp[i][7]+=dp[i-1][3]+dp[i-1][7];
            }
        }
    }
    ans+=dp[n][0]+dp[n][2]+dp[n][4]+dp[n][6];//因为最后一个后一位必定是0
    cout<<ans<<endl;
}

方法二 递推

解题思路:
我们换种思路,其实前两个状态固定了的话,后面的状态其实已近被固定了,所以我们只需要枚举前两个的状态位,递推出后面的状态,如果可以的话就计入答案,也就是说,答案最多有4种方案

代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    
    
    int n;
    int a[10010]={
    
    0};
    int dp[10010]={
    
    0};
    cin>>n;
    for(int i=0;i<n;i++){
    
    
        cin>>a[i];
    }
    int ans=0;
    for(int i=0;i<4;i++){
    
    //枚举四个状态
        dp[0]=i&1;//初始化
        dp[1]=(i>>1)&1;
        if(dp[0]+dp[1]!=a[0]){
    
    
            continue;
        }
        for(int j=1;j<n;j++){
    
    
            dp[j+1]=a[j]-dp[j-1]-dp[j];
            if(dp[j+1]<0||dp[j+1]>1){
    
    //下一位只能是1或者0
                break;
            }
            if(j==n-1&&dp[n]==0){
    
    //符合条件,最后一位必须是0
                ans++;
            }
        }
    }
    cout<<ans<<endl;
}

2、牛可乐的翻转游戏

NC235250 牛可乐的翻转游戏

题目描述

  牛可乐发明了一种新型的翻转游戏!
  在一个有 nn 行 mm 列的棋盘上,每个格子摆放有一枚棋子,每一枚棋子的颜色要么是黑色,要么是白色。每次操作牛可乐可以选择一枚棋子,将它的颜色翻转(黑变白,白变黑),同时将这枚棋子上下左右相邻的四枚棋子的颜色翻转(如果对应位置有棋子的话)。
  牛可乐想请你帮他判断一下,能否通过多次操作将所有棋子都变成黑色或者白色?如果可以,最小操作次数又是多少呢?

输入描述:

  第一行两个整数 n,m(1≤n≤100,1≤m≤10),代表棋盘的行数和列数。
  之后 n 行,每行 m 个数字,第 i 个数字如果为 0 ,代表对应位置的棋子为白色,如果为 1 则为黑色。

输出描述:

  如果无法将所有棋子变成一个颜色,输出 “Impossible”(不含引号),否则输出变成一个颜色的最小操作次数。

示例1
输入
4 4
1001
1101
1001
1000

输出
4

解题思路:
  如果第一行的翻转已经被确定了,那么第二行的操作也会被确定,因为第二行的翻转需要把第一行全部翻转成1或是0,所以我们只需要枚举第一行的翻转,判断该翻转是否满足题意即可,m<=10,那么最多有210种翻转方案

代码:

#include<bits/stdc++.h>
using namespace std;
int getch(int val){
    
    
    int ans=0;
    while(val){
    
    
        ans++;
        val&=(val-1);
    }
    return ans;
}
int main(){
    
    
    int n,m;
    int a[105]={
    
    0};
    int dp1[105]={
    
    0};
    int dp2[105]={
    
    0};
    int ans=0x3f3f3f3f;
    cin>>n>>m;
    for(int i=0;i<n;i++){
    
    
        string b;
        cin>>b;
        for(int j=0;j<m;j++){
    
    
            a[i]*=2;
            if(b[j]=='1'){
    
    
                a[i]+=1;
            }
        }
    }
    for(int i=0;i<(1<<m);i++){
    
    //(1<<m)个状态
        int sum1=0;
        int sum2=0;
        sum1+=getch(i);
        sum2+=getch(i);
        dp1[0]=a[0]^i^(i>>1)^((i<<1)&((1<<m)-1));//全部变1
        dp1[1]=a[1]^i;
        dp2[0]=a[0]^i^(i>>1)^((i<<1)&((1<<m)-1));//全部变0
        dp2[1]=a[1]^i;
        for(int j=1;j<n;j++){
    
    //后面的翻转已近被固定了
            int ch1=dp1[j-1]^((1<<m)-1);
            int ch2=dp2[j-1];
            sum1+=getch(ch1);
            sum2+=getch(ch2);
            dp1[j]=dp1[j]^ch1^(ch1>>1)^((ch1<<1)&((1<<m)-1));
            dp1[j+1]=a[j+1]^ch1;
            dp2[j]=dp2[j]^ch2^(ch2>>1)^((ch2<<1)&((1<<m)-1));
            dp2[j+1]=a[j+1]^ch2;
        }
        if(dp1[n-1]==(1<<m)-1){
    
    //最后一排全部是1
            ans=min(ans,sum1);
        }
        if(dp2[n-1]==0){
    
    //最后一排全部是0
            ans=min(ans,sum2);
        }
    }
    if(ans==0x3f3f3f3f){
    
    
        cout<<"Impossible"<<endl;
    }else{
    
    
        cout<<ans<<endl;
    }
}

是不是很简单呢?

刚接触肯定会觉得难,多些做题多些用,熟悉了就容易了,兄弟萌,加油!!!

文章尚有不足,欢迎大牛们指正

感谢观看,点个赞吧

猜你喜欢

转载自blog.csdn.net/weixin_52115456/article/details/128460505