文章目录
上课部分内容
题目计分
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皇后问题
同样他也有非常多的变体,如果你又双叒叕有兴趣的话,也可以看我写的另另另一篇变体解析八皇后问题