当前位置: 首页 > article >正文

蓝桥杯训练day3

day3

  • 1.递推
    • (1)3777. 砖块
    • (2)95. 费解的开关
    • (3)1208. 翻硬币
  • 2.递归
    • (1)1497. 树的遍历
    • (2)97. 约数之和

1.递推

(1)3777. 砖块

在这里插入图片描述
思路:由于最终只有两种状态,全是B,全是W。
所以尝试两种情况。
对于全是B的情况:从字符串第一位开始,如果当前字符不是B,那么当前字符和下一个字符都变。然后同时记录当前字符的下标。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;


int T;
int n;

string str;

void update(char &c)
{
    if(c=='W')c='B';
    else c='W';
}

bool check(char c)
{
    vector<int>res;  //记录答案
    string s=str;
    int n=s.length();
    for(int i=0;i<n-1;i++)
    {
        if(s[i]!=c)
        {
            update(s[i]);
            update(s[i+1]);
            res.push_back(i);
        }
    }
    if(s.back()!=c)return false;
    cout<<res.size()<<endl;
    for(int i=0;i<res.size();i++)
        cout<<res[i]+1<<" ";    //为什么加1,因为题目规定下标从1开始
    if(res.size())cout<<endl;
    return true;
}
int main()
{
    cin>>T;
    while(T--)
    {
        cin>>n>>str;
        if(!check('W')&&!check('B'))puts("-1");  //如果全白或者全黑都不行,输出-1
    }
    return 0;    
}

(2)95. 费解的开关

在这里插入图片描述

思路:
首先明白规则,动一个数,他周围四个数和他本身都需要改变。
我们需要对其中的若干个数进行改变,且改变的的数不能超过6个。使最终所有数都变成1.

我们一行一行的点亮所有灯,前面层的点亮后再取点亮后面层的灯。如果当前四次灯都被点亮后,第五行的灯没有被点亮,那么则说明永远不能点亮。因为前4层都点亮了,此时去点亮第五层,第五层一动,第四层又会变化。每一层的0都应该由下一层操作来处理变成1,因为这样就不会影响到上一层已经处理好的1。

首先可以保证这样一层一层来点亮灯一定可以得到答案,只是步数不一定小于等于6.
那么要保证步数最小,这与什么有关呢??不知道。但是可以知道的是,无论是哪种操作方案,一旦决定了第一步,按照上面的定义,一层一层来递推,得到的答案是唯一的。而第一步就是对第一层操作。那么就穷举第一层的操作,然后固定第一层,从第二层开始去点亮上面一层的灯。

也许说的难懂。请看这个题解:
添加链接描述

#include<iostream>
#include<cstring>

using namespace std;

char g[10][10];
char backup[10][10];

int n;

void turn(int x,int y)
{
    int dx[5]={0,-1,0,1,0};
    int dy[5]={0,0,1,0,-1};
    
    for(int i=0;i<5;i++)
    {
        int a=x+dx[i],b=y+dy[i];
        if(a<0||b<0||a>=5||b>=5)continue;
        if(g[a][b]=='0')g[a][b]='1';
        else g[a][b]='0';
    }
}


int main()
{
    cin>>n;   //n组数据
    while(n--)
    {
        //输入数据
        for(int i=0;i<5;i++)cin>>g[i];
        
        int ans=7;
        //枚举第一行的所有开关选择方案
        for(int k=0;k<1<<5;k++)  //00000-11111   即一个都不按到全都按下。
        {
            
            int step=0;
            memcpy(backup,g,sizeof g);  //每次都将初始状态拷贝到“操作数组”
            
            //看看第一行按了哪些个,并且需要按照规则改变周边的灯
            for(int i=0;i<5;i++)
            {
                if((k>>i)&1)  //1表示按,0表示不按
                {
                    step++;
                    turn(0,i);
                }
            }
            
            //上面固定第一行后,然后操作第二行,操作第二行使第一行都变成1.然后操作第三行,使第二行都变1
            //一直操作到第5行,使第四行全变成1,最后第五行不能操作了,因为操作第五行会改变第四行已经
            //是1的状态。最后判断第五行是否都是1,如果不是,则不能使灯泡全亮
            
            bool ok=true;
            
            for(int i=0;i<4;i++)  //观察第i行,然后用i+1行取改变i行状态
            {
                for(int j=0;j<5;j++)
                {
                    if(g[i][j]=='0')
                    {
                        step++;
                        if(step>6)  //剪枝一下
                        {
                            ok=false;
                            break;
                        }
                        turn(i+1,j);
                    }
                }
                if(!ok)break;
                    
            }
            
           if(ok)
           {
                int i=0;
                for(i;i<5;i++)
                    if(g[4][i]=='0')break;
                if(i==5)
                    ans=min(ans,step);
            }
            
            //记得还原备份
            memcpy(g,backup,sizeof backup);
        }
        
        if(ans<=6)
            cout<<ans<<endl;
        else
            cout<<"-1\n";
        
    }
    
    return 0;
}

(3)1208. 翻硬币

在这里插入图片描述

思路:
就近原则,遇到一样的就不变,遇到不一样的就翻转。贪心思想。为什么会得到最小哎,还没有证明。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

string start;
string target;


int ans;


void update(char& c)
{
    if(c=='*')c='o';
    else c='*';
}

int main()
{
    cin>>start;
    cin>>target;
    
    //先尝试一下就近原则
    int n=start.size();
    for(int i=0;i<n-1;i++)
    {
        if(start[i]!=target[i])
        {
            update(start[i]);
            update(start[i+1]);
            ans++;
        }
    }
    
    cout<<ans<<endl;
    return 0;
    
}

2.递归

(1)1497. 树的遍历

在这里插入图片描述

思路:经典二叉树问题,很入门的递归的题目,但是要手写非递归方式有点考验能力。
递归的小诀窍,尝试解决问题,往里面解决三层,然后发现规律,然后利用规律写递归方程。考虑边界情况和递归最底层的处理。

先构造,再遍历。
关键几点:

  1. 了解前中后序遍历的规则
  2. 了解递归的本质:重复做步骤一样的事情,搭建好框架。
  3. 理解层次遍历的规则,结合队列的性质,层次遍历可以用队列的性质来实现
  4. 比较难理解的一点:找区间(就是l1-k-1-…那些)
#include<iostream>
#include<cstring>
#include<unordered_map>
using namespace std;

const int N=31;

int postorder[N],inorder[N];

unordered_map<int,int>lchild;   //记录树的节点(层次遍历)  lchild[i]=j表示i的左孩子是j
unordered_map<int,int>rchild;  //同理

unordered_map<int,int>Hash;  //存储中序遍历每个节点的下标

int n;


int dfs(int l1,int r1,int l2,int r2) ///[l1,r1]表示中序遍历的区间,[l2,r2]表示后续遍历的区间
{
    if(l1>r1)return 0;
    
    int root=postorder[r2];  //根节点
    int k=Hash[root];   //根节点在中序遍历里面的位置
    
    lchild[root]=dfs(l1,k-1,l2,l2+(k-1-l1));  //构造左孩子  [中左,中右,后左,后右](k-1-l1)表示的是左孩子的长度-1,l2+上它就表示左孩子的右端点
    rchild[root]=dfs(k+1,r1,l2+(k-1-l1)+1,r2-1);   //构造右孩子
    return root;
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>postorder[i];
    for(int i=0;i<n;i++)
    {
        cin>>inorder[i];
        Hash[inorder[i]]=i;   //这一步是后面的一个优化过程,一开始不理解很正常
    }
        
    
    int root=dfs(0,n-1,0,n-1);  //找到根节点
    
    
    int q[N];
    int hh=0,tt=-1;
    if(root)  //找到根节点
        q[++tt]=root;
        
    while(hh<=tt)  //实际上队列的行为正好符号层次遍历的规则
    {
        int t=q[hh++];
        
        //看当前节点的左右孩子节点是否存在
        if(lchild[t])
            q[++tt]=lchild[t];
        if(rchild[t])
            q[++tt]=rchild[t];
        
    }
    
    for(int i=0;i<=tt;i++)
        cout<<q[i]<<" ";
        
    return 0;
}

(2)97. 约数之和

在这里插入图片描述
这道题需要用到一些数学基础知识,否则写的很困难。可以参考一下:

  1. 试除法求约数
  2. 筛选质因数
  3. 约数之和
  4. 约数个数
  5. 快速幂
  6. 秦九邵算法

过程比较复杂,需要做好一写就一天的思想准备,一步一步来,巩固基础,然后做出这道题。

#include<iostream>
#include<cstring>
#include<cmath>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int mod=9901;

ll res=1;

unordered_map<int,int>Hash;


ll qmid(ll a,ll b)  //a的b次幂
{
	ll ans=1;
	while(b)
	{
		if(b&1)
			ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}


ll sum(int p,int k)  //递归求和,等价于其他方式,可能速度快一点,因为用到了快速幂
{
	if(k==1)
		return 1;
	if(k%2==0)
		return (qmid(p,k/2)+1)*sum(p,k/2)%mod;
	else	
		return (qmid(p,k-1)+sum(p,k-1))%mod;
}



int main()
{
	int a,b;
	cin>>a>>b;
	//第一步,分解a,将其变为质因数的幂次相乘
	int t=a;
	for(int i=2;i<=t/i;i++)  //筛选质因数
	{
		while(t%i==0)
		{
			Hash[i]++;
			t/=i;
		}
	}
	if(t>1)Hash[t]++;

	//第二步:求值
	for(auto prime:Hash)
	{
		ll x=prime.first;
		ll y=prime.second*b;
		res=res*sum(x,y+1)%mod;
	}
	
	if(a==0)res=0;
	cout<<res<<endl;
	return 0;
}

http://www.kler.cn/a/2468.html

相关文章:

  • javaScriptBOM
  • 浅谈目前我开发的前端项目用到的设计模式
  • imx6ull qt多页面控制系统(正点原子imx系列驱动开发)
  • Linux实现两台服务器之间ssh连接
  • 虚拟机断网没有网络,需清理内存,删除后再重启
  • 【MAC】深入浅出 Homebrew 下 Nginx 的安装与配置指南
  • 深入理解JVM虚拟机(六)
  • 梳理LVM逻辑卷管理,
  • JDBC
  • C++虚函数与多态
  • ChatGPT推出第四代GPT-4!不仅能聊天,还可以图片创作!
  • 【图神经网络 文献精读】针对SARS-CoV-2大流行的改进和优化的药物再利用方案
  • 生成时序异常样本-学习记录-未完待续
  • 毕业设计 基于51单片机自动智能浇花系统设计
  • 【十二天学java】day05--数组和循环高级
  • 01 | Msyql系统架构
  • 学习系统编程No.7【进程替换】
  • 从地图到手机通讯到ChatGPT,你想要的免费 API 都给你整理好了
  • uni-app:登录与支付-- 三秒后自动跳转
  • SLF4J、Log4J、Log4J2、Logback之间是什么关系
  • 【linux】:进程控制
  • java各大集合的区别
  • 推荐一款免费开源的OCR软件
  • 菜鸟刷题Day1
  • GPIO四种输入和四种输出模式
  • 最优化算法 - 动态规划算法