[算法课]全面翻新计划!第四周全解

上课部分内容

题目计分

1 某电视台举办了低碳生活大奖赛。题目的计分规则相当奇怪:每位选手需要回答10个问题(其编号为1到10),越后面越有难度。答对的,当前分数翻倍;答错了则扣掉与题号相同的分数(选手必须回答问题,不回答按错误处理)。每位选手都有一个起步的分数为10分。某获胜选手最终得分刚好是100分,如果不让你看比赛过程,你能推断出他(她)哪个题目答对了,哪个题目答错了吗?如果把答对的记为1,答错的记为0,则10个题目的回答情况可以用仅含有1和0的串来表示。例如:0010110011 就是可能的情况。

两种方法来完成,做对比
方法1:直观的思考方法是枚举法。暴力破解。
涉及到编号很容易联想到一个知识点:数组,因为数组有编号。数组的编号实际是从0开始的。所以因为是10道题目,且编号是1-10,所以我准备定义数组长度是11,可以废掉第0个不适用。使用的编号范围是1-10的数组元素。其中数组元素的值就是0或者1。
因为枚举会涉及到for循环。刚好虽然这里是有2个数值0和1,那可以用循环来表示,0到1的取值范围。

颜老板版本

枚举
#include<iostream>

using namespace std;

int main()
{
    for(int i=0;i<=1<<10;i++)
    {   

        int tmpans = 10;
        for(int j=0;j<10;j++)
            if(i>>j&1)tmpans*=2;
            else tmpans -=j;
        
            //if(tmpans == 100)
            //{
            for(int j=0;j<10;j++)if(i>>j&1)cout<<1;else cout<<0;cout<<endl;
           // }
    }   
    return 0;
}


#include<stdio.h>
int main()
{
	int a[11],s;//提醒自己数组使用的是a[1]到a[10]
	
	for(a[1]=0;a[1]<=1;a[1]++)
	for(a[2]=0;a[2]<=1;a[2]++)
	for(a[3]=0;a[3]<=1;a[3]++)
	for(a[4]=0;a[4]<=1;a[4]++)
	for(a[5]=0;a[5]<=1;a[5]++)
	for(a[6]=0;a[6]<=1;a[6]++)
	for(a[7]=0;a[7]<=1;a[7]++)
	for(a[8]=0;a[8]<=1;a[8]++)
	for(a[9]=0;a[9]<=1;a[9]++)
	for(a[10]=0;a[10]<=1;a[10]++)
	{
		int i; 
		s=10;//所有可能在判断之前都要准备好开始的分数是10分
		 
		for(i=1;i<=10;i++)
		{
			if(a[i]==0)//答错了
				s=s-i;
			if(a[i]==1)
				s=s*2; 
		}
		
		if(s==100)
		{
			for(i=1;i<=10;i++)
				printf("%d",a[i]);
			printf("\n");
		}
	}

	return 0;	
} 

时间复杂度:o(n^11)
空间复杂度:o(n)

dfs版本

方法2: 【采用递归的解题思路】目的解决如果问题放大的话,代码长的问题。

因为每道题的判断都必须包含对与错(注意每题都要讨论对与错,不是2选1)

10个题目的判断过程应该是一样的。(所谓求解方法是一致的。)【符合递归的思路】

10个题目判断结束了		
#include<stdio.h>
int a[11];//定义全局数组

void f(int s,int i)//所谓参数就是要完成该功能必须已知的数据:起始分数和题号 
{
	if(i>10)//终结条件,注意当i==10,表示判断到第10题,那应该继续判断 
	{
		if(s==100)//输出是有条件的
		{
			int j;
			for(j=1;j<=10;j++)
				printf("%d",a[j]);
			printf("\n");			
		}	
	}
	else//递归算法 ,题目还没有判断完,应该继续判断 
	{
		//注意每题都要讨论对与错,不是2选1
		//讨论此第i题错的时候
		a[i]=0;
		f(s-i,i+1);
		//讨论此第i题对的时候
		a[i]=1;
		f(s*2,i+1); 
	}	
}

int main()
{
	f(10,1);//起始分总10分开始,题目从第1题开始 
	return 0;	
} 	

更新版

二进制枚举写法

用二进制枚举,用01表示和查找所有可能

#include<iostream>

using namespace std;

int main()
{
    for(int i=0;i<=1<<10;i++)//二进制状态下左移10个位置 INT为1024
        {
            int tmpNum=10;
            for(int j=0;j<10;j++)
                if(i>>j&1)tmpNum*=2;//检查 i向右移动j位之后,当前这个数字是否是1
                else tmpNum-=j+1;
                
            if(tmpNum==100)
            {
                for(int j=0;j<10;j++)
                    if(i>>j&1)cout<<1;
                    else cout<<0;
                cout<<endl;
            }
        }
    
}
dfs版本

正常的dfs思路

#include<iostream>

using namespace std;

const int N=10;
bool st[N+5];

void dfs(int sum,int u)
{
    if(u>N)//表明十道题都答完了
    {
        if(sum==100)
            {
                for(int i=1;i<=10;i++)
                    if(st[i])cout<<1;
                    else cout<<0;
                
                cout<<endl;
            }
        return ;
    }
    
        
    if(st[u]==false)//选择的状态
    {
        st[u]=true;
        dfs(sum*2,u+1);
        st[u]=false;
    }
    
    st[u]=false;//不选的状态
    dfs(sum-u,u+1);
}

int main()
{
    dfs(10,1);
    
    return 0;
}

答案

0010110011
0111010000
1011010000

打印图形

rank=3
在这里插入图片描述
rank=5
在这里插入图片描述
rank=6
在这里插入图片描述

分析

在这里插入图片描述

在这里插入图片描述

rank决定了什么?
行 列
rank=1 1 2
rank=2 2 4
rank=3 4 8
rank=4 8 16
rank=5 16 32

rank 2^(rank-1) 2^rank

接下来继续

整个问题最大的是填写 ,而在计算机中是属于字符型

所以建议可以使用一个二维数组【字符型的二维数组,整个二维数组可以直接初始化为全部空格】

剩下的事情就是填写*的问题。

用递归思想来完成。

经过刚才的分析,整个问题实际上是处理3个大块的问题。

而每个大块都是处理 填写*

印证了 【处理方法是一致的!!!,所以使用递归思想】

3个块的填写方法是一致的。【因为图形都一样】

不同的是在填写的位置上是不同的!

黄色区域的填写起始位置【顶部块】行是首行,列+行数/2
蓝色区域的填写起始位置【左下角】行是行+行数/2,列
绿色区域的填写起始位置【右下角】行是行+行数/2,列+行数

颜老板版本

#include<stdio.h>
#define N 200
#include"math.h"
void f(char a[N][N],int rank,int hang,int lie)
{
	int hangshu;
	
	hangshu=pow(2,rank-1);//rank决定了图形的行数 
	
	if(rank==1)//终结条件 
	{
		a[hang][lie]='*';
		return ; 
	}
	else//递归算法 
	{
		//黄色区域的填写起始位置【顶部块】行是首行,列+行数/2
		f(a,rank-1,hang,lie+hangshu/2);
		//蓝色区域的填写起始位置【左下角】行是行+行数/2,列
		f(a,rank-1,hang+hangshu/2,lie);
		//绿色区域的填写起始位置【右下角】行是行+行数/2,列+行数	
		f(a,rank-1,hang+hangshu/2,lie+hangshu);
	}
}

int main()
{
	char a[N][N];
	int i,j,rank;
	
	//让字符数组全部初始化为空格
	for(i=0;i<N;i++)
	for(j=0;j<N;j++)
		a[i][j]=' ';
	
	printf("输入rank的值:");	
	scanf("%d",&rank);
	
	f(a,rank,0,0);//整个图形是从第0行第0列开始
	
	for(i=0;i<N;i++)
	{
		for(j=0;j<N;j++)	
	 		printf("%c",a[i][j]);
		printf("\n"); 
	} 
	return 0;	
}

吐槽

这道题目很可能不考,因为没有什么用单纯在折腾脑子
当然我不是说所有的图形题
这里我把我写的原题解析贴出来,有兴趣可以详细查看原题考察思路

了解更多

打印图形

下课内容

看图编树

在这里插入图片描述

吐槽

看着就麻烦,之后再更新

图选数字

在一个由 n 行 m 列的方格组成的地图上,每个方格上有一个数,你要取出一些数,使得他们的和值最大。
但是有一个条件,你选取的数中,任意 2 个数所在的方格都不能相邻。
2 个方格相邻就是指他们共享一条边。
在这里插入图片描述

说明

:如果挑了91,则30和48和90和60是不能挑的!
1存在比较大小的问题。
书写一个宏定义,或者书写一个求最大值的自定义函数
#define max(a,b) (a>b?a:b)
2 还需要存储原始数据的数组
int num[10][10];
3 每个位置上的数值存在用 或者 不用的处理问题。该数组中每个位置上都是数值是0或者1表示,0表示没有使用,1表示使用了。
int visit[10][10];
4 题目中说:你选取的数中,任意 2 个数所在的方格都不能相邻。
不能相邻就是该选中数值的上,下,左,右的数值不能选!!!
所以在程序中需要判断其上下左右的数值选了还是没有选的问题。
选中的数值与其上下左右的标号关系是什么?
行 列
上 +1 +0
下 -1 +0
左 +0 -1
右 +0 +1
需要一个上下左右的数组的位置,只是为了处理方便
int dir[4][2]={{1,0},{-1,0},{0,-1},{0,1}};
5 讨论每个数值的时候,还要讨论一个问题:在不在数组的范围之内?用0表示不在,1表示在1.
可以书写一个自定义函数
int in(int x,int y)
{
if(0<=x && x<n && 0<=y && y<m)
return 0;
else
return 1;
}

颜老板思路

部分代码

#include<stdio.h>
#define max(a,b) (a>b?a:b)
int n,m;//方格的行数和列数
int num[10][10]; //存放原始值 
int visit[10][10];//存放原始值的	选中或者没有选中的	1 或者0 ,全局数组全部默认为0 
int dir[4][2]={{1,0},{-1,0},{0,-1},{0,1}};//表示判断数据的上下左右的位置

int in(int x,int y)
{
	if(0<=x && x<n && 0<=y && y<m)	return 0;
	else	return 1;
}

//完成选中数值的过程,自定义函数的编写
 
 
int main()
{
	n=6,m=4;
	int a[6][4]={	48, 30, 39, 87,
					48, 91, 90, 22,
					36, 60, 41, 28,
					49, 96, 37, 88,
					87, 71, 96, 66,
					15, 24, 75, 55	
	};
	int i,j;
	for(i=0;i<6;i++)
	for(j=0;j<4;j++)
		num[i][j]=a[i][j];
		
	//函数的调用
	
	//结束的输出 
	
	return 0;	
}

数据

6 4
48  30  39  87 
48  91  90  22 
36  60  41  28 
49  96  37  88 
87  71  96  66 
15  24  75  55

答案

749

AC代码

本来我全部局部数据,结果到最后数组需要静态数据我拿不出来局部的,吐了
而且写了6个参数,太烦了,还是老老实实全局走起来

正常dfs思路

#include<iostream>
#include<cstring>

using namespace std;

int dx[]={-1,0,1,0},dy[]={0,1,0,-1};//上下左右四个偏移量

const int N=1e4;//我没有给固定数字,相当于把这道题转换为自由给定数据的题目,但是最大NM只能在1e4左右,毕竟最大也就2E9
int g[N][N];
bool st[N][N];

void dfs(int x,int y,int tmpn,int &sum,int row,int col)
{
    if(y==col)x++,y=0;//是这样的,我把y理解成列,col就是列的数量,走完了就加一个行数,我看行
    if(x==row)
        {
            sum = max(sum,tmpn);
            return ;
        }
    
    else 
    {
        bool f = true;
        for(int i=0;i<4;i++)//判断一下合法
        {
            int tmpx=x+dx[i],tmpy=y+dy[i];
            if(tmpx<0||tmpx>=row||tmpy<0||tmpy>=col)continue;//边界
            if(st[tmpx][tmpy]){f=false;break;}//四周是否有目标值
        }
        
        if(f)
        {
            st[x][y]=true;   
            dfs(x,y+1,tmpn+g[x][y],sum,row,col);//选择
            st[x][y]=false;
        }
        
        dfs(x,y+1,tmpn,sum,row,col);//不选
    }
}

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=0;i<n;i++)    
        for(int j=0;j<m;j++)
            cin>>g[i][j];

    int res=0;
    dfs(0,0,0,res,n,m);
    
    cout<<res;
    
    return 0;
}

吐槽

这个思路其实和N皇后非常相似
但是给半截属实不必

了解更多

首先你可能需要更加详细的了解这道题,因此你可以我写的详细解析图解数字
其次你可能需要了解与这个思路非常密切的N皇后问题,因此你也可以查看我写的N皇后问题
同样他也有非常多的变体,如果你又双叒叕有兴趣的话,也可以看我写的另另另一篇变体解析八皇后问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

俺叫西西弗斯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值