递推算法

文章首发于:My Blog 欢迎大佬们前来逛逛

递推

递推是一种常见的数学方法,也是计算机科学中常用的算法。

递推的基本思想是通过已知的一些值来推导出其他未知值的方法

递推在计算机科学中广泛应用于算法设计和数据结构中。 递推的应用非常广泛,比如在计算斐波那契数列时,可以通过递推来计算第n个斐波那契数。

斐波那契数列的定义是:

F(0) = 0,F(1) = 1,F(n) = F(n-1) + F(n-2)。

通过递推可以得到:

F(0) = 0

F(1) = 1

F(2) = F(1) + F(0) = 1

F(3) = F(2) + F(1) = 2

F(4) = F(3) + F(2) = 3

F(5) = F(4) + F(3) = 5

F(6) = F(5) + F(4) = 8

可以看到,通过递推可以很方便地计算出斐波那契数列的各项值。递推的关键是通过已知的一些值来计算未知的值。在斐波那契数列中,已知的是F(0)和F(1),通过这两个值可以递推出其他的值。 递推的思想不仅在数学中有应用,在计算机科学中也非常重要。

比如,在动态规划中,递推是一种常见的解决方法,在动态规划中,通常需要通过递推来计算出最优解。比如,在计算机科学中,背包问题是一个经典的动态规划问题。在背包问题中,需要计算出在给定的约束条件下,能装下的最大价值的物品。通过递推可以很方便地计算出最优解。

3777. 砖块

3777. 砖块

题目要求:给你一个只有’W'和‘B’组成的字符串,每次可以选择一个位置i,并且会自动包含位置i+1,然后把这两个位置的字符改成相反的字符直到字符串只包含一个字符为止,请你判断有没有解,如果有则输出改变的次数,没有解则输出-1

示例:

8
BWWWWWWB

i=1 与 i=2 修改:BBBWWWWB

i=3 与 i=4 修改:BBBBBWWB

i=5 与 i=6 修改:BBBBBBBB

所以只需要修改三次即可,输出3.

4
BWBB

无法修改这个为同一个字符,无解,输出-1


这道题利用了 贪心+递推的思想。

递推:简单说利用前面的递推出后面的。

那么这道题该如何利用递推呢?

可以观察到:第 i 个位置的修改会影响 i+1 位置一起修改,当我们从 i 位置遍历移动到 i+1 的位置的时候,我们的此时的 i 位置已经被上一次操作所影响。所以这就是一个递推的过程。

对于这道题的求解思路:

我们只能对相邻的两个字符进行操作
我们可以分别对这个字符串进行‘W‘检查和’B‘检查,如果都不能满足,则输出-1表示无解

观察一些性质:

对第一个字符 s[0] 的操作:

  • 如果检查字符ch’W’,如果s[0]’W’,则s[0]一定无须操作;

  • 同理如果检查字符ch’B’,s[0]’B’,则s[0]也一定无须操作。

即如果第一个字符和目标字符相同,则我们一定无须替换这个字符,因为操作一次会互换,操作两次就是回到了原来。
因此我们只能选择对某一个位置操作一次,操作两次就是又变了回来,因此操作两次无意义。

因此对于所有的字符都是同理,如果某位置字符与目标字符相同,则我们跳过它,直到遇到某位置的字符与目标字符不同,这便是贪心的思想,相同就跳过,不相同就跳过。

则我们改变这个位置和下一个位置的字符为相反的,然后把这个位置记录一下。

最后:如果我们可以对倒数第二个位置进行操作,则说明最后一个位置也会跟着操作,此时分为两种情况:

  1. [n-1]和[n]操作后,[n]位置的字符等于检查字符,则为true,说明有解
  2. [n-1]和[n]操作后,[n]位置的字符不同于检查字符,则为false,说明无解

因为我们无法对最后一个字符进行单独操作,因此根据最后一个字符来判断是否有解

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
#define int long long
const int N=1e5+10;
int nums[N];
string s;
int n,t;
void change(char& ch){
    if (ch=='W') ch='B';
    else ch='W';
}
bool check(char ch){
    //贪心+递推
    string temp=s;
    vector<int> vec;
    for (int i=0;i+1<n;i++){
        if (temp[i]!=ch){
            //改变
            change(temp[i]);
            change(temp[i+1]);
            vec.push_back(i);//记录改变的位置
        }
    }
    //根据最后一个字符来判断有无解
    if (temp.back()!=ch) return false;
    
    cout<<vec.size()<<endl;
    for (auto x:vec){
        cout<<x+1<<' ';
    }
    if (vec.size()){
        cout<<endl;
    }
    return true;
}
signed main(){
    cin>>t;
    while (t--){
        cin>>n>>s;
        if (!check('W') && !check('B')) cout<<-1<<endl;
    }
    return 0;
}

95. 费解的开关

95. 费解的开关

题目要求:5*5的01矩阵,让每次对一位进行操作,会影响到其周围上下左右的四个数字,由0变为1,或者由1变为0,要求能否在6次操作内将全部都变为1


本题也是一道非常经典的递推习题。

并且和上一道题也优有点类似,上一道题是一维的递推,这一道题是二维的递推,其实把这个二维的竖着往下看,其实可以看作一个粗一点的一维的,本质上是一致的。

需要明白,灯的影响顺序总是从上到下影响的,即当前行的当前列影响下一行的当前列

第一行的某一个位置如果为0,则对应其下一行的这个位置一定需要改变

同理第二行为0,则可以确定第三行一定需要按

第三行为0,则可以确定第四行也一定需要按


因此确定递推关系:根据上一行确定下一行

那么显然第一行需要单独处理,并且我们需要枚举第一行的所有情况,就像《砖块》一样,我们对首元素的枚举是基于‘W’和'B'字符进行的,因此首元素等于W的时候,递推一次;首元素等于B的时候,递推一次,然后对这两种枚举进行归纳。

如果把W和B看作是一种情况,则这道题就是有32种情况,因为每一个01组成的五位二进制数都是不同的,一共有32种,则可以看作第一行枚举32次

我们把第一行二进制 00000-11111,枚举第一行的32种情况0-31,则我们讨论这31种情况,对于每一种情况我们都进行1->2 2->3 3->4 4->5两行之间的递推,直到遍历完这32种情况,其中每一种都需要统计这种方法的最小次数。

最后一行用于判断有无解,因为最后一行如果全为1,则一定有解;如果存在一个0,则一定无解。
由于第四行可以推出第五行,因此最后一行我们只需要用来判断是否有解即可。

#include <bits/stdc++.h>
using namespace std;
const int INF=99999999;
int n;
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1};//五个方向
char nums[10][10];
void change(int x,int y){
    for (int i=0;i<5;i++){
        int cx=x+dx[i],cy=y+dy[i];
        if (cx<0 || cx>=5 || cy<0 || cy>=5) continue;
        nums[cx][cy]^=1;//相反按
    }
}
int check(){
    int res=INF;//操作次数
    for (int p=0;p<32;p++){//遍历32种方法
        int ones=0;
        int temp[10][10];
        memcpy(temp,nums,sizeof(nums));
        //处理第一行
        for (int i=0;i<5;i++){
            if (p>>i&1){//关于此处的为什么为1是按?不应该为0的时候才按吗,其实这里是按照p来对第一行的所有操作进行枚举,而不是灯的开关情况。
                ones++;
                change(0,i);//对第一行进行操作
            }
        }
        //根据前一行处理下一行
        for (int i=0;i<4;i++){
            for (int j=0;j<5;j++){
                if (nums[i][j]=='0'){//上一行为零,则这一行一定操作这个位置
                    ones++;
                    change(i+1,j);//处理下一行
                }
            }
        }
        bool fg=false;
        //判断最后一行是否全1
        for (int i=0;i<5;i++){
            if (nums[4][i]=='0'){
                fg=true;
                break;
            }
        }
        if (!fg){
            //成功,则统计最小值
            res=min(res,ones);
        }
        memcpy(nums,temp,sizeof(nums));
    }
    if (res>6){
        //6步以内进行操作
        return -1;
    }
    return res;
}
int main(){
    cin>>n;
    while (n--){
        for (int i=0;i<5;i++){
            cin>>nums[i];
        }
        cout<<check()<<endl;
    }
    return 0;
}
posted @ 2023-03-29 12:41  hugeYlh  阅读(85)  评论(0编辑  收藏  举报