一、第六题 寒假作业
题目
现在小学的数学题目也不是那么好玩的。看看这个寒假作业:
□ + □ = □
□ - □ = □
□ × □ = □
□ ÷ □ = □
每个方块代表1~13中的某一个数字,但不能重复。比如:
6 + 7 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5
以及:
7 + 6 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5
就算两种解法(加法,乘法交换律后算不同的方案)。你一共找到了多少种方案?请填写表示方案数目的整数。注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
1.补充知识点
本题要补充全排列的两种方法:交换法和选择法
(1)交换法生成全排列模板:
static int []a =new int [N];//此数组为存储需要排列的全部元素
static void genenrateP(int K){
if (k==N)//由于数组可访问的最大下标为K-1.所以当到达K时,完成一次排列
{
if(check()){
//输出要执行的内容
}
return ;
}
//这个位置还可以设置剪枝
for( int i=k;i<N;i++){
//交换a[i]与a[k]元素
{ int t=a[i];a[i]=a[k];a[k]=a[i];}
//进行下一个元素的交换
generateP(K+1);
//回溯
{ int t=a[i];a[i]=a[k];a[k]=a[i];}
}
}
(2) 选择法生成全排列模板
①如果需要排列的数字不是一串连续的数字时
static int []a =new int [N];//此数组为存储需要排列的全部元素
static boolean []marked=new boolean [N]//此为判断a数组哪个元素是被访问过的,如果a中的第i个元素被访问过时,marked[i]=true,因此,一开始要初始化marked为false
static int path[]=new int [N];//该数组为存取每一次的全排列,即在a中选择一个元素后存入path
static void generateP(int k){
if (k==N){
if(check()){
//执行需要的内容
}
return;
}
for(int i=0;i<N;i++)//这是与交换法最大的区别,他要从头开始遍历需要排列的元素
{
//在这个位置还可以设置提前剪枝 ,如果选取的元素里面有重复元素的话
if (i > 0 && a[i] == a[i - 1] && !vis[i - 1]) continue;//现在准备选取的元素和上一个元素相同,但是上一个元素还没被使用
//以后都加上这一句话进行剪枝
if(!marked[i])//如果没被选择
{
marked[i]=true;
path[k]=a[i];
generateP(k+1);
//注意最后要回溯
marked[i]=false;
}
}
}
②如果需要排列的数字是一串连续的数字时
//如果,需要排列的数组时一串连续的数字,比如说是1~13,那么还可以这样写,即省去a[i]数组
static boolean []marked=new boolean [14]//此为判断哪个元素是被访问过的,如果i被访问过时,marked[i]=true,因此,一开始要初始化marked为false
static int path[]=new int [14];//该数组为存取每一次的全排列,即在a中选择一个元素后存入path
static void generateP(int k){
if (k==14){
if(check()){
//执行需要的内容
}
return;
}
for(int i=1;i<14;i++)//这是与交换法最大的区别,他要从头开始遍历需要排列的元素
{
//在这个位置还可以设置提前剪枝 a[]数组存的是选取元素,如果选取的元素里面有重复元素的话
if (i > 0 && a[i] == a[i - 1] && !vis[i - 1]) continue;//现在准备选取的元素和上一个元素相同,但是上一个元素还没被使用
//以后都加上这一句话进行剪枝
if(!marked[i])//如果没被选择
{
marked[i]=true;
path[k]=i;
generateP(k+1);
//注意最后要回溯
marked[i]=false;
}
}
}
2.本题解题思路
直接用交换法全排列1~13,然后判断最后结果的前12位是否满足公式,可以利用提前剪枝加快效率,注意利用交换法剪枝的时候要把交换回来才能够continue
3.本题代码
public class _06寒假作业 {
static int a[]={1,2,3,4,5,6,7,8,9,10,11,12,13};
static int ans;
static boolean check(){
if(a[0]+a[1]==a[2]&&
a[3]-a[4]==a[5]&&
a[6]*a[7]==a[8]&&
a[9]%a[10]==0&&
a[9]/a[10]==a[11])
return true;
return false;
}
static void f(int k){
if(k==13){
if(check()){
System.out.printf("%d+%d=%d %d-%d=%d %d*%d=%d %d/%d=%d\n",
a[0],a[1],a[2],
a[3],a[4],a[5],
a[6],a[7],a[8],
a[9],a[10],a[11]);
ans++;
}
}
for (int i = k; i < 13; ++i) {
{int t=a[i];a[i]=a[k];a[k]=t;}
// 提前排除,提升效率
if((k==2&&a[0]+a[1]!=a[2]) || k==5&&a[3]-a[4]!=a[5]){
//注意提前剪枝时要交换回来
{int t=a[i];a[i]=a[k];a[k]=t;}
continue;
}
f(k+1);
{int t=a[i];a[i]=a[k];a[k]=t;}
}
}
public static void main(String[] args) {
f(0);
System.out.println(ans);
}
}
二、第七题 剪邮票
题目
有12张连在一起的12生肖的邮票。现在你要从中剪下5张来,要求必须是连着的(即两张邮票有一条公共边,仅仅连接一个角不算相连)。用如下表格表示12张邮票:
0表示没有被剪,1表示剪去,则如下表格为一个合格的剪取:
请填写表示方案数目的整数。注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
1.本题涉及到的知识点
(1) 连通性检测
假如有一张图,然后将图用二维数组表示,这个图中,被剪掉的位置就设为1,然后要检测被剪掉的邮票有多少条连通分支,此时,这个的总体思路就是:对二维数组进行扫描,当遇到元素为1的时候,对它进行深搜,注意,此时深搜不是平常那样每次只朝着一个方向搜,而是四面八方都要搜,当有出现1的时候,就进行往下四面八方搜,等到搜完后,连通分支数就++。
连通性检测的模板如下:
static int check(int g[][]) {
//g为存储图的二维数组
int cnt = 0;//连通块的数目
// g上面就有5个格子被标记为1,然后用dfs做连通性检查,这几个标为1的属于几个连通块
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
if (g[i][j] == 1) {
dfs(g, i, j);
cnt++;
}
}
}
return cnt;
}
//下面为连通性检测专属的dfs
static void dfs(int g[][], int i, int j) {
//注意,要把该位置标为0,防止重复
g[i][j] = 0;
//四个方向都要去检测,满足条件则继续搜,注意不能超过边界
if (i - 1 >= 0 && g[i - 1][j] == 1) dfs(g, i - 1, j);
if (i + 1 <= 2 && g[i + 1][j] == 1) dfs(g, i + 1, j);
if (j - 1 >= 0 && g[i][j - 1] == 1) dfs(g, i, j - 1);
if (j + 1 <= 3 && g[i][j + 1] == 1) dfs(g, i, j + 1);
}
(2) 将一维数组的位置映射到二维数组的位置
方法:遍历一遍二维数组进行赋值,假设二维数组a有X行Y列,它的一个元素[i][j],就相当于一维数组的path[iX+J]元素,所以赋值的时候直接 a[i][j]=path[iX+J]就行,下面为映射的模板:
static void Change(int path[]) {
int g[][]=new int[3][4];
// 将某个排列映射到二维矩阵上
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
if (path[i * 4 + j] == 1) g[i][j] = 1;
else g[i][j] = 0;
//上述的if else可以直接变成
//g[i][j]=path[i*4+j],一个道理的,因为path里面除了0,就是1
}
}
2.本题的解题思路:
一开始是想着对每邮票的每一个点进行一个深搜,当到达边界且步数为5时,就count++,但是在以往的例题就说过,如果要用这种思路的时候,要重点考虑是否会出现T字形的情况,如果出现了的话,那么这种方法就y不适合,要转换思路。
这题可以转化为,在12张邮票里面,随便抽五个数,然后判断他们的连通分分支是不是只有一条。
对于12张邮票里面随便抽五个数我们可以转化为,在一个长度为12的一维数组里面,全排列5个1,有1的就是要抽的,这便是随便抽五个数的全部情况,注意为了加快效率,要用选择法进行全排列,并且要加上那句话进行剪枝
所以这道题一共划分为3个函数:生成全排列函数,连通性检查函数,深搜函数
3.代码如下
public class _07剪邮票 {
static int a[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1};//它的每个排列代表着12选5的一个方案
static int ans;
static void dfs(int g[][], int i, int j) {
g[i][j] = 0;
if (i - 1 >= 0 && g[i - 1][j] == 1) dfs(g, i - 1, j);
if (i + 1 <= 2 && g[i + 1][j] == 1) dfs(g, i + 1, j);
if (j - 1 >= 0 && g[i][j - 1] == 1) dfs(g, i, j - 1);
if (j + 1 <= 3 && g[i][j + 1] == 1) dfs(g, i, j + 1);
}
static boolean check(int path[]) {
int g[][]=new int[3][4];
// 将某个排列映射到二维矩阵上
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
if (path[i * 4 + j] == 1) g[i][j] = 1;
else g[i][j] = 0;
}
}
int cnt = 0;//连通块的数目
// g上面就有5个格子被标记为1,现在才用dfs做连通性检查,要求只有一个连通块
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
if (g[i][j] == 1) {
dfs(g, i, j);
cnt++;
}
}
}
return cnt == 1;
}
static boolean vis[]=new boolean[12];
static void f(int k, int path[]) {
if (k == 12) {
if (check(path)) {
ans++;
}
}
for (int i = 0; i < 12; ++i) {
if (i > 0 && a[i] == a[i - 1] && !vis[i - 1]) continue;//现在准备选取的元素和上一个元素相同,但是上一个元素还没被使用
if (!vis[i]) {//没有被用过的元素可以抓入到path
vis[i] = true;//标记为已访问
path[k] = a[i];//将a[i]填入到path[k]中
f(k + 1, path);//递归
vis[i] = false;//回溯
}
}
}
public static void main(String[] args) {
int path[]=new int[12];
f(0,path);
System.out.println(ans);
}
}
三、第八题 取球博弈
题目
两个人玩取球的游戏。
一共有N个球,每人轮流取球,每次可取集合{n1,n2,n3}中的任何一个数目。
如果无法继续取球,则游戏结束。
此时,持有奇数个球的一方获胜。
如果两人都是奇数,则为平局。
假设双方都采用最聪明的取法,
第一个取球的人一定能赢吗?
试编程解决这个问题。
输入格式:
第一行3个正整数n1 n2 n3,空格分开,表示每次可取的数目 (0<n1,n2,n3<100)
第二行5个正整数x1 x2 … x5,空格分开,表示5局的初始球数(0<xi<1000)
输出格式:
一行5个字符,空格分开。分别表示每局先取球的人能否获胜。
能获胜则输出+,
次之,如有办法逼平对手,输出0,
无论如何都会输,则输出-
例如,输入:
1 2 3
1 2 3 4 5
程序应该输出:
- 0 + 0 -
再例如,输入:
1 4 5
10 11 12 13 15
程序应该输出:
0 - 0 + +
再例如,输入:
2 3 5
7 8 9 10 11
程序应该输出:
- 0 0 0 0
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
1.解题思路
这是典型的博弈问题,我们一开始就可以用递归的思想,即球的总数是变化的,两人手中的球数也是变化的,将这三个参数作为递归传参,每次都去试探选择三种取法的情况,进行深搜判断,递归得出口就是球的总数小于最小的取球数,注意由于是博弈问题,所以需要进行下一次搜索时,需要变化you 和me
2.本题的注意点
要注意分析递归过程中,有哪些参数是在变化的,变化的参数即要传到递归函数中
3.暴力代码如下
public class a{
Scanner sc=new Scanner (System.in);
int []n=int [3];
n[0]=sc.nextInt();
n[1]=sc.nextInt();
n[2]=sc.nextInt();
//对数组进行排序,从小到大
Array.sort(n);
for(int i=0;i<5;i++){
int num=sc.nextInt();
char res=f(num,0,0);//返回博弈结果
System.out.print(res+" ");
}
System.out.println();
}
/**
* 参数代表着当前取球人面临的局面
* @param num 球的总数
* @param me 我方持有的数目
* @param you 对手持有的数目
* @return
*/
private static char f(int num,int me,int you){
if(num<n[0])//不够数,递归出口
{
if((me&1)==1&&(you&1)==0) //如果我手中为奇数,对手手中为偶数
{
return '+';
}
else if ((me&1)==0&&(you&1)==1) return '-';
else return '0';
}
boolean ping =false;
//对于每一种取球情况进行深搜
for(int i=0;i<3;i++){
if(num>n[i]) {
char res = f(num - n[i], you, me);//这个是博弈问题的关键,要互换
if (res == '-') return '+';
if (res == '0') ping = true;
}
}
if(ping) return '0';
else return '-';
}
4.记忆化递归代码如下
public class _08取球博弈 {
private static int[] n;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = new int[3];
for (int i = 0; i < 3; i++) {
n[i] = sc.nextInt();
}
Arrays.sort(n);//排序
for (int i = 0; i < 5; i++) {
int num = sc.nextInt();
char res = f(num, 0, 0);
System.out.print(res + " ");
}
System.out.println();
}
static char[][][]cache = new char[1000][2][2];
/**
* 参数代表着当前取球人面临的局面
* @param num 球的总数
* @param me 我方持有的数目-->我方数目的奇偶性
* @param you 对手持有的数目-对方数目的奇偶性
* @return
*/
private static char f(int num, int me, int you) {
if (num<n[0])//不够取
{
if ((me&1)==1&&(you&1)==0)return '+';
else if ((me&1)==0&&(you&1)==1)return '-';
else return '0';
}
if (cache[num][me][you]!='\0')return cache[num][me][you];
boolean ping = false;
for (int i = 0; i < 3; i++) {
if (num >= n[i]) {
char res = f(num - n[i], you, (n[i]&1)==0?me:(1-me));//注意此处,传递me和you的奇偶性
if (res == '-')
{
cache[num][me][you]='+';
return '+';
}
if (res == '0')
ping = true;
}
}
//如果能走到第这行,说明不存在对手输的情况,那么是否存在平的情况
if (ping)
{
cache[num][me][you]='0';
return '0';
}
else
{
cache[num][me][you]='-';
return '-';
}
}
}