剪格子

1462690283

摘要:本题是2013年第四届蓝桥杯全国软件大赛预赛A组第9题,编程题。
题目描述

标题:剪格子
如图p1.jpg所示,3 x 3 的格子中填写了一些整数。

我们沿着图中的红色线剪开,得到两个部分,每个部分的数字和都是60。
本题的要求就是请你编程判定:对给定的m x n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等。
如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。
如果无法分割,则输出 0

输入输出格式

程序先读入两个整数 m n 用空格分割 (m,n<10)
表示表格的宽度和高度
接下来是n行,每行m个正整数,用空格分开。每个整数不大于10000
程序输出:在所有解中,包含左上角的分割区可能包含的最小的格子数目。

例如:

输入

3 3
10 1 52
20 30 1
1 2 3

输出

3

输入

4 3
1 1 1 1
1 30 80 2
1 1 1 100

输出

10

资源约定
峰值内存消耗(含虚拟机) < 64M
CPU消耗 < 5000ms
解题思路

最简单最暴力的不过DFS,题目讲得很清楚,分成两个区域,求包含左上角格子的格子数,那么可以先求出平均值,然后从左上角开始遍历标记走过的位置然后加上当前的格子数值,不满足条件则回溯。

题目有毒,说是要输入m n,按正常人理解谁不是m行n列,后边看的时候才知道是n行m列,老毛病又犯哎,难怪我说第二个例子一直调试不出来。
代码实现

import java.util.Scanner;

/**

  • 剪格子

  • @author Ape

  • /
    public class DFS_4a9 {

    static int[][] map = new int[101][101];
    static int[][] vis = new int[101][101];
    static int[][] dir = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } };
    static int n, m, ans, num, pValue = 0;

    static void dfs(int x, int y, int value) {

    if (x < 0 || y < 0 || x >= m || y >= n)
        return;
    if (value > pValue)
        return;
    if (value == pValue) {
        num = ans;
        return;
    }
    for (int i = 0; i < 4; i++) {
        int tx = x + dir[i][0];
        int ty = y + dir[i][1];
        if (tx < 0 || ty < 0 || tx >= m || ty >= n)
            continue;
        int tvalue = value + map[tx][ty];
        if (vis[tx][ty] == 0 && tvalue <= pValue) {
            // 标记
            vis[tx][ty] = 1;
            ans++;
            dfs(tx, ty, tvalue);
            // 回溯
            ans--;
            vis[tx][ty] = 0;
        }
    }

    }

    public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    n = scanner.nextInt();
    m = scanner.nextInt();
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++) {
            map[i][j] = scanner.nextInt();
            pValue += map[i][j];
        }
    if (pValue % 2 == 1) // 若是奇数,输出0
        System.out.println("0");
    else {
        pValue = pValue / 2;
        // 从左上角开始
        vis[0][0] = 1;
        ans = 1;
        dfs(0, 0, map[0][0]);
        System.out.println(num);
    }
    scanner.close();

    }
    }

八皇后

1462707762

问题描述

八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题。
该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放
八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或
同一斜线上,问有多少种摆法。
解题思路

一个再经典不过的回溯算法,题目一来,可以直接给他下DFS,一开始其实我是用一个二维数组进行标记,原来还可以很巧妙的使用一位数组的下标跟对应值的关系进行验证,那么思路有了,即是从第一行开始遍历,那么问题又来了,怎么样判断他不合规律捏,横着竖着来就不用说了,斜着的话得思考思考啦,不过也不难,举个例子就可以发现他们的关系,即是纵坐标之差的绝对值跟横坐标之差的绝对值的相等的,没错,就是这样。

回溯点就是每一次进行标记的点与之前标记的点进行规制判断,符合则继续n+1到下一行寻找标记点,不符合则继续在当前行+1的列上标记判断…..
代码实现

public class NQueen {
static final int N = 8; // 皇后的数目
static int ans = 0;
static int[] vis = new int[N];

// n代表行数(即vis数组下标),vis[n]表示列数,所以记当前坐标为(n,vis[n]),数组下标跟跟对应的值
static void dfs(int n) {
    if (n == N) {
        ans++;
    } else
        for (int i = 0; i < N; i++) {
            vis[n] = i;
            if (check(n))// 满足条件继续执行
                dfs(n + 1);
        }
}

static boolean check(int k) {// 判断是否在一条直线上或者一条斜线上
    for (int j = 0; j < k; j++)
        if (Math.abs(vis[k] - vis[j]) == Math.abs(k - j)
                || vis[j] == vis[k])
            return false;
    return true;
}

public static void main(String[] args) {
    dfs(0);
    System.out.println(ans);
}

}

“数独”游戏

1462715676

题目描述

你一定听说过“数独”游戏。

如【图1.png】,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
数独的答案都是唯一的,所以,多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
例如:

输入

005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700

输出

145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764

输入

800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400

输出

812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 2000ms

解题思路

这种题不是第一次了,DFS+回溯,大家应该都玩过数独,规则上面也说了,当然,我们不可能以我们的思维去解题,要以计算机的思维去解题,计算机最大优势就是运算速度快,所以呀,按照正常人的想法肯定是一行一行的从左到右验证搜索,当然,只要你喜欢也可以一列一列来,你中意就好。

这题跟八皇后很像,每一步都需要验证,符合的则继续往下搜索,否则回溯。

要注意控制的条件是当不为0时跳过,当到达最后一列时应该往下一行的开头继续搜索。

现在思路就非常清晰了,那么问题又来了,就是判断验证x行y列的数,这个也可以很简单的实现,代码如下
代码实现

public class Shudu {

static int[][] map = new int[9][9];
static boolean flag = false;

static void dfs(int x, int y) {
    if (flag)
        return;
    if (x > 8) {
        flag = true;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++)
                System.out.print(map[i][j] + " ");
            System.out.println();
        }
        return;
    }
    if (y > 8) // 走到最右边时,x+1到下一行的第一列开始,即y=0
        dfs(x + 1, 0);
    else if (map[x][y] != 0) // 如果不等于0继续向右走
        dfs(x, y + 1);
    else
        for (int i = 1; i <= 9; i++)
            if (check(x, y, i)) { // 验证是否符合
                map[x][y] = i;
                dfs(x, y + 1); // 继续搜索下一列y+1
                map[x][y] = 0; // 回溯
            }
}

static boolean check(int x, int y, int value) {
    // 验证行跟列
    for (int i = 0; i < 9; i++)
        if (map[x][i] == value || map[i][y] == value)
            return false;
    // 验证位于当前小的那个九宫格
    for (int i = x / 3 * 3; i <= x / 3 * 3 + 2; i++)
        for (int j = y / 3 * 3; j <= y / 3 * 3 + 2; j++)
            if (map[i][j] == value)
                return false;
    return true;
}

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    for (int i = 0; i < 9; i++)
        for (int j = 0; j < 9; j++)
            map[i][j] = scanner.nextInt();
    dfs(0, 0);
    scanner.close();
}

}

图的遍历

图的遍历

1462756149

由于一系列的原因,早上我要去找老师给我买票,所以我要去上课!并非出自我本意,本学期的数据结构就上过第一周的2节课,今天是第二次来上的课。

嗯,是的,来了就应该听听,今天讲的是图,说到图的遍历,还提了一下DFS,BFS深度广度搜索法,但是没具体讲,于是,在他讲课的时候我又用DFS,BFS水了一段代码。
问题描述

图的遍历

如图

1.深度优先遍历(DFS)

基本思想:首先从图中某个顶点v0出发,访问此顶点,然后依次从v0相邻的顶点出发深度优先遍历,直至图中所有与v0路径相通的顶点都被访问了;若此时尚有顶点未被访问,则从中选一个顶点作为起始点,重复上述过程,直到所有的顶点都被访问。可以看出深度优先遍历是一个递归的过程。

其深度优先遍历得到的序列为:

0->1->3->7->4->2->5->6

2.广度优先遍历(BFS)

基本思想:首先,从图的某个顶点v0出发,访问了v0之后,依次访问与v0相邻的未被访问的顶点,然后分别从这些顶点出发,广度优先遍历,直至所有的顶点都被访问完。

其广度优先遍历得到的序列为:

0->1->2->3->4->5->6->7

解题思路

遍历,可以采用dfs,bfs,可以使用一个二维数组记录顶点与顶点的关系,例如map[1][2] = 1,map[2][1] = 0, 那就可以表示顶点1->2(1可以通向2,2不能通向1),map[3][4] = 1,map[4][3] = 1,可以表示为3-4,互通。然后再用一个一位数组标记遍历过的顶点。
代码实现

public class Graph {

static final int MAX = 100;
static int[][] map = new int[MAX][MAX];
static int[] vis = new int[MAX];
static int v; // 顶点数

static void dfs(int n) {
    if (n == v)
        return;
    vis[n] = 1; // 标记第n个顶点
    System.out.print(n + "->");
    for (int i = 0; i < v; i++)
        if (map[n][i] == 1 && vis[i] == 0) // 当第i个顶点与n顶点相通,且i顶点未标记则以i顶点继续遍历
            dfs(i);
}

static void bfs() {
    Queue<Integer> queue = new LinkedList<Integer>();
    // 把开始遍历的顶点加入队列并标记
    queue.offer(0);
    vis[0] = 1;
    System.out.print(0);
    while (!queue.isEmpty()) {
        int top = queue.poll();// 出列
        for (int i = 0; i < v; i++)
            if (map[top][i] == 1 && vis[i] == 0) {
                System.out.print("->" + i);
                // 标记入列
                vis[i] = 1;
                queue.offer(i);
            }
    }
}

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    v = scanner.nextInt();
    int i, j;
    // 默认输入为无向图
    while (scanner.hasNext()) {
        i = scanner.nextInt();
        j = scanner.nextInt();
        if (i <= v && j <= v && i >= 0 && j >= 0 && map[i][j] == 0) {
            map[i][j] = 1;
            map[j][i] = 1;
        } else
            break;
    }
    // dfs(0);
    bfs();
    scanner.close();
}

}

大臣的旅费

1462895514

摘要:本题是2013年第四届蓝桥杯全国软件大赛预赛A组第10题,编程题。
题目描述

标题:大臣的旅费
很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
J是T国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。他有一个钱袋,用于存放往来城市间的路费。
聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。
J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式

输入的第一行包含一个整数n,表示包括首都在内的T王国的城市数

城市从1开始依次编号,1号城市为首都。

接下来n-1行,描述T国的高速路(T国的高速路一定是n-1条)

每行三个整数Pi, Qi, Di,表示城市Pi和城市Qi之间有一条高速路,长度为Di千米。
输出格式

输出一个整数,表示大臣J最多花费的路费是多少。
样例输入1

5
1 2 2
1 3 1
2 4 5
2 5 4

样例输出1

135

输出格式

大臣J从城市4到城市5要花费135的路费。
解题思路

题目一览而过,不就是DFS深度记忆化搜索嘛
代码实现

import java.util.Scanner;

public class A10 {

static final int MAX = 1001;
static int[][] map = new int[MAX][MAX];
static int[] vis = new int[MAX];
static int num, p, q, d, maxValue = 0;

static void dfs(int v, int value) { // v当前顶点,value表示当前的值
    maxValue = value > maxValue ? value : maxValue;
    vis[v] = 1; // 标记走过
    // 遍历所有城市
    for (int i = 1; i <= num; i++)
        if (map[v][i] != 0 && vis[i] == 0) { // 找到可以通行的城市i
            value += map[v][i];
            dfs(i, value);
            // 回溯
            value -= map[v][i];
            vis[i] = 0;
        }
    vis[v] = 0;
}

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    num = scanner.nextInt();
    for (int i = 0; i < num - 1; i++) {
        p = scanner.nextInt();
        q = scanner.nextInt();
        d = scanner.nextInt();
        map[p][q] = map[q][p] = d;
    }
    // 分别从第i个城市开始遍历
    for (int i = 1; i <= num; i++)
        dfs(i, 0);
    int sum = 0;
    for (int i = 11; i <= 10 + maxValue; i++)
        sum += i;
    System.out.println(sum);
    scanner.close();
}

}

运行结果

思路是对的,我不知道为什么只得了75分,可恨的是不给我看测评数据,还要VIP我去,肯定是优化什么的问题吧我想

想想这样就能拿A组第10题的75分也该知足了…预赛时候的第10题我可能就25分都没有

大神写的代码100分…
大神代码

import java.util.*;

public class Main {
public static void main(String args[]) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
QiDian[] QiDians = new QiDian[n];
for (int i = 0; i < n; i++) {
QiDians[i] = new QiDian(i, new ArrayList());
}
int tem1 = 0;
int tem2 = 0;
int quanzhong = 0;
for (int i = 0; i < n - 1; i++) {
tem1 = scan.nextInt();
tem2 = scan.nextInt();
quanzhong = scan.nextInt();
QiDians[tem1 - 1].list.add(new ZhongDian(quanzhong,
QiDians[tem2 - 1]));
QiDians[tem2 - 1].list.add(new ZhongDian(quanzhong,
QiDians[tem1 - 1]));
}
int[] data = search(QiDians[0], null);
int[] data2 = search(QiDians[data[1]], null);
int sum = 0;
for (int i = 1; i <= data2[0]; i++) {
sum += i + 10;
}
System.out.println(sum);
}

public static int[] search(QiDian q, QiDian p) {
    int[] data = new int[] { 0, q.index };
    for (int i = 0; i < q.list.size(); i++) {
        if (q.list.get(i).qidian.equals(p) == false) {
            int[] data2 = search(q.list.get(i).qidian, q);
            int tem = q.list.get(i).quanzhong + data2[0];
            if (tem > data[0]) {
                data[0] = tem;
                data[1] = data2[1];
            }
        }
    }
    return data;
}

}

class QiDian {
int index;
ArrayList list = new ArrayList();

public QiDian(int index, ArrayList<ZhongDian> list) {
    super();
    this.index = index;
    this.list = list;
}

}

class ZhongDian {
int quanzhong;
QiDian qidian;

public ZhongDian(int quanzhong, QiDian qidian) {
    super();
    this.quanzhong = quanzhong;
    this.qidian = qidian;
}

}

买不到的数目

1463375044

摘要:本题是2013年第四届蓝桥杯全国软件大赛预赛C组第9题,编程题。
问题描述

小明开了一家糖果店。他别出心裁:把水果糖包成4颗一包和7颗一包的两种。糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。大于17的任何数字都可以用4和7组合出来。

本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。
输入格式

两个正整数,表示每种包装中糖的颗数(都不多于1000)
输出格式

一个正整数,表示最大不能买到的糖数

样例输入1

4 7

样例输出1

17

样例输入2

3 5

样例输出2

7

解题思路

Dp,动态规划
代码实现

import java.util.Scanner;

public class A9 {

static final int MAX = 1000001;
static int a, b;
static int[] m = new int[MAX];

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    a = scanner.nextInt();
    b = scanner.nextInt();
    m[a] = 1;
    m[b] = 1;
    int t = a > b ? a : b;
    for (int i = t + 1; i < MAX; i++)
        if (m[i - a] == 1 || m[i - b] == 1)
            m[i] = 1;
    for (int i = MAX - 1; i >= 0; i--)
        if (m[i] == 0) {
            System.out.print(i);
            break;
        }
    scanner.close();
}

}

最大子矩阵

1464055387

问题描述

给定一个n*m的矩阵A,求A中的一个非空子矩阵,使这个子矩阵中的元素和最大。

其中,A的子矩阵指在A中行和列均连续的一块。
输入格式

输入的第一行包含两个整数n, m,分别表示矩阵A的行数和列数。

接下来n行,每行m个整数,表示矩阵A。
输出格式

输出一行,包含一个整数,表示A中最大的子矩阵中的元素和。
样例输入

3 3
-1 -4 3
3 4 -1
-5 -2 8

样例输出

10

样例说明

取最后一列,和为10。
数据规模和约定

对于50%的数据,1<=n, m<=50;

对于100%的数据,1<=n, m<=500,A中每个元素的绝对值不超过5000。
解题思路

像题目要求最大什么什么,大多都用Dp。其实刚刚接到题目我也有点慌了,自己最不拿手就是Dp,像这道题,矩阵无非就是用一个二维数组存储的数据,

那么先要对矩阵进行压缩,可以以行为主序,也可以以列为主序,拿行来举例

2 5 -1
7 -5 3
0 8 -11

这一组数据,我们很容易可以看出来最大子矩阵是17

2 5
7 -5
0 8

没错,这是一个最大的子矩阵,其实你可以这样去分解他,以行为主序那他就成了

9 8 -8

这不就成了求最大子段问题啦,哈哈,没想到吧

那么就要对他进行压缩了,一开始我没想到有更好的办法,只好每两个列的差值都存储,但后来发现了一个巧妙的方法,那就是取他的行差或者列差

像上面的数据,可以这样进行压缩,就是把每列的值够累计相加,得出以下的数据

2 5 -1
9 0 2
9 8 -8

很容易理解,第一行就是原来矩阵第一行的子矩阵,最后一行就是整个矩阵,第二行就是原来矩阵的第一第二行压缩出来的,如果想得到原来矩阵的第二第三行压缩矩阵,那么拿压缩后的矩阵的第三行减第一行,有没有。哈哈
代码实现

import java.util.Scanner;

/**

  • 最大子矩阵

  • @author Ape

  • /
    public class MaxJz {

    static int[][] a = new int[510][510];
    static int n, m, t, sum, max;

    public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    n = scanner.nextInt();
    m = scanner.nextInt();
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            t = scanner.nextInt();
            a[i][j] = a[i - 1][j] + t; // 压缩矩阵
        }
    max = Integer.MIN_VALUE;
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++) {
            sum = 0;
            for (int k = 1; k <= m; k++) {
                t = a[j][k] - a[i - 1][k]; // 计算第i行到第j行的差
                if (sum > 0)
                    sum += t;
                else
                    sum = t;
                if (sum > max)
                    max = sum;
            }
        }
    System.out.println(max);
    scanner.close();

    }

}

ps:距离第七届蓝桥杯决赛还有3天

再探四方定理

1464058514

题目描述
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多4个正整数的平方和。
如果把0包括进去,就正好可以表示为4个数的平方和。
比如:
5 = 0^2 + 0^2 + 1^2 + 2^2
7 = 1^2 + 1^2 + 1^2 + 2^2
(^符号表示乘方的意思)

对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对4个数排序:
0 <= a <= b <= c <= d
并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法
程序输入为一个正整数N (N<5000000)
要求输出4个非负整数,按从小到大排序,中间用空格分开
例如,输入:

5

则程序应该输出:

0 0 1 2

再例如,输入:

12

则程序应该输出:

0 2 2 2

再例如,输入:

773535

则程序应该输出:

1 1 267 838

资源约定:
峰值内存消耗(含虚拟机) < 256M

CPU消耗 < 3000ms

这是今年省赛的题目,不得不说,当时我竟用dfs搞不出

题目原文链接

解法一(这是当时赛后想出来比较完美的解法)

public class A9 {

static int[] mpt = new int[5000010];
static int n;

static void init() {
    for (int i = 0; i * i <= n; i++)
        for (int j = 0; j * j <= n; j++)
            if (i * i + j * j <= n)
                mpt[i * i + j * j] = 1;
}

public static void main(String[] args) {
    boolean flag = false;
    Scanner scanner = new Scanner(System.in);
    n = scanner.nextInt();
    init();
    for (int i = 0; i * i <= n; i++) {
        for (int j = 0; j * j <= n; j++) {
            if (mpt[n - i * i - j * j] == 0)
                continue;// 如果剩下的差用两个完全平方数不能组合出来就不继续
            for (int k = 0; k * k <= n; k++) {
                int temp = n - i * i - j * j - k * k;
                double w = Math.sqrt(temp);
                if (w == (int) w) {
                    System.out.printf("%d %d %d %d ", i, j, k, (int) w);
                    flag = true;
                    break;
                }
            }
            if (flag)
                break;
        }
        if (flag)
            break;
    }
    scanner.close();
}

}

解法二(DFS)

import java.util.Scanner;

public class SiFangshu {

// 从最大数开始验证
static boolean f(int number, int[] a, int n) {
    if (number == 0)
        return true;
    if (n == 4)
        return false;
    for (int i = (int) Math.sqrt(number); i >= 1; i--) {
        a[n] = i;
        if (f(number - i * i, a, n + 1))
            return true;
    }
    return false;
}

// 从0开始验证
static boolean w(int number, int[] a, int n) {
    if (number == 0)
        return true;
    if (n == 4)
        return false;
    for (int i = 0; i <= (int) Math.sqrt(number); i++) {
        a[n] = i;
        if (w(number - i * i, a, n + 1))
            return true;
    }
    return false;
}

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int[] a = new int[] { 0, 0, 0, 0 };
    w(n, a, 0);
    System.out.println(a[0] + "-" + a[1] + "-" + a[2] + "-" + a[3]);
    scanner.close();
}

}

实现起来用DFS简单,但是有时缺还不如解法一速度快,因为有些数是没有0的

如果是从高位开始那就是DFS快。

不过从低位开始遍历也是可以优化的

九宫重排

1464138053

不得不说的八数码之九宫重排
距离第七届蓝桥杯决赛还有2天
问题描述

如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。

我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。

输入格式
输入第一行包含九宫的初态,第二行包含九宫的终态。
输出格式
输出最少的步数,如果不存在方案,则输出-1。
样例输入

12345678.
123.46758

样例输出

3

样例输入

13524678.
46758123.

样例输出

22

解题思路

由此题可以联想到八数码问题,百度查了资料有各种A各种IDA等等解法

此题我先使用了BFS,发现有两个问题,第一是状态转换,二是状态的标记,很麻烦

网上有种标记法是康托展开,是根据a10!+a21!+a32!+a43!+…+a9*8!相加得出的hash值

这个hash值就是当前表示的状态,那么就很方便了

后来我用了5的次方根表示hash码,运算速度比上面的hash快一倍,但是发现有一组测试数据不通过,所以生成的hash还不是唯一的,不能使用

我用的是a15^0+a25^1+a35^2+…+a95^8

那么状态的转换又是一个问题,Java没有C语言C++那么有个swap函数,一开始我用了原来状态每次进行状态变换

发现最后全部值都为0!这是一个很严重的问题,原来每次都没有把值给换过来,后来想了想用了个备份临时数组每次获取他对他进行操作

Oh yeah~终于搞掂晒

后来又用了Java集合类HashMap+BFS双搜算法,速度明显快很多

BFS双搜算法是两个队列分别从起始状态跟目标状态开始,然后每次都相互判断Hash码是否存在以达到可通行出路,即互通状态
BFS+Hash 实现代码

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

/**

  • BFS+HASH 九宫重排

  • @author Ape

  • /
    public class BfsHash {
    static final int MAX = 3;
    static final int[] hash = { 1, 1, 2, 6, 24, 120, 720, 5020, 40320 }; // Hash生成规则
    static int[][] dir = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } }; // 移动规则
    static int[] vis = new int[400000];
    static int ans, sx, sy;
    static BfsHash a = new BfsHash();

    class Node {

    int[][] map = new int[MAX][MAX];
    int x, y, hash, h; // h为深度,即步数
    
    // map的备份,切记不能直接进行复制
    int[][] getCopyMap() {
        int[][] copy = new int[MAX][MAX];
        for (int i = 0; i < MAX; i++)
            for (int j = 0; j < MAX; j++)
                copy[i][j] = map[i][j];
        return copy;
    }
    
    Node() {
    }
    
    Node(int x, int y, int h, int[][] map, int hash) {
        this.x = x;
        this.y = y;
        this.h = h;
        this.map = map;
        this.hash = hash;
    }

    }

    // 检查越界
    static boolean check(int x, int y) {

    if (x < 0 || y < 0 || x >= MAX || y >= MAX)
        return false;
    return true;

    }

    // 生成Hash码,康托展开
    static int getHash(int[][] map) {

    int[] t = new int[MAX * MAX];
    int k = 0, tHash = 0;
    for (int i = 0; i < MAX; i++)
        for (int j = 0; j < MAX; j++)
            t[k++] = map[i][j];
    for (int i = 0; i < 9; i++) {
        k = 0;
        for (int j = 0; j < i; j++)
            if (t[i] < t[j])
                k++;
        tHash += k * hash[i];
    }
    return tHash;

    }

    static int getHash2(int[][] map) {

    int hash = 0, k = 1;
    for (int i = 0; i < MAX; i++)
        for (int j = 0; j < MAX; j++) {
            hash += k * map[i][j];
            k *= 5;
        }
    return hash;

    }

    static int bfs(Node start) {

    Queue<Node> queue = new LinkedList<Node>();
    queue.offer(start);
    Node top = a.new Node();
    int tx, ty, thash, th;
    while (!queue.isEmpty()) {
        top = queue.poll(); // 出列
        if (top.hash == ans)
            return top.h;
        for (int i = 0; i < 4; i++) {
            tx = top.x + dir[i][0];
            ty = top.y + dir[i][1];
            if (!check(tx, ty))
                continue;
            // TODO 注意,切记不能直接get原来的map,而且复制map的时候不能直接赋值给一个数组,要遍历赋值
            // TODO
            // 因为直接拿原来的map下一次循环的top.map就不说出列时候的那个,所以每次换位都需要一个临时的数组,并生成hash
            int[][] tmap = top.getCopyMap();
            tmap[top.x][top.y] = tmap[tx][ty];
            tmap[tx][ty] = 0;
            thash = getHash(tmap);
            if (vis[thash] == 0) {
                th = top.h + 1;
                vis[thash] = 1; // 标记
                queue.offer(a.new Node(tx, ty, th, tmap, thash)); // 入列,要重新new一个
            }
        }
    }
    return -1;

    }

    // 字符串转int数组
    static int[][] string2int(String string) {

    char[] ch = string.toCharArray();
    int[][] m = new int[MAX][MAX];
    for (int i = 0; i < ch.length; i++)
        if (ch[i] != '.')
            m[i / 3][i % 3] = ch[i] - '0';
        else {
            m[i / 3][i % 3] = 0;
            // 起始空格位置
            sx = i / 3;
            sy = i % 3;
        }
    return m;

    }

    public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    String start = scanner.next();
    String end = scanner.next();
    Node s = a.new Node();
    Node e = a.new Node();
    s.map = string2int(start);
    s.hash = getHash(s.map);
    s.x = sx;
    s.y = sy;
    vis[s.hash] = 1;
    
    e.map = string2int(end);
    e.hash = getHash(e.map);
    ans = e.hash; // ans为目标状态的hash值
    
    System.out.println(bfs(s));
    scanner.close();

    }

}

BFS双向搜索 代码实现

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

/**

  • 九宫重排

  • @author Ape

  • /
    public class JiuGong {

    static HashMap<String, Integer> hash1, hash2; // 使用Java集合类的MapHash
    static int sx, sy, ex, ey, index;
    static char[][] start = new char[3][3], end = new char[3][3];
    static String startString, endString;
    static int[][] dir = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } }; // 移动规则

    public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    startString = scanner.next();
    endString = scanner.next();
    long a = System.currentTimeMillis();
    index = 0;
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++) {
            start[i][j] = startString.charAt(index);
            end[i][j] = endString.charAt(index);
            if (start[i][j] == '.') {
                sx = i;
                sy = j;
            }
            if (end[i][j] == '.') {
                ex = i;
                ey =  ;
            }
            index++;
        }
    Node node1 = new Node(start, sx, sy, 0);
    Node node2 = new Node(end, ex, ey, 0);
    Queue<Node> queue1 = new LinkedList<Node>();
    Queue<Node> queue2 = new LinkedList<Node>();
    hash1 = new HashMap<String, Integer>();
    hash2 = new HashMap<String, Integer>();
    hash1.put(startString, 0);
    hash2.put(endString, 0);
    queue1.offer(node1);
    queue2.offer(node2);
    System.out.println(bfs(queue1, queue2));
    System.out.println(System.currentTimeMillis() - a);
    scanner.close();

    }

    static boolean check(int x, int y) {

    if (x < 0 || y < 0 || x >= 3 || y >= 3)
        return false;
    return true;

    }

    static int bfs(Queue queue1, Queue queue2) {

    while (!queue1.isEmpty() || !queue2.isEmpty()) {
        if (!queue1.isEmpty()) {
            Node node1 = queue1.poll(); // 出列
            if (hash2.containsKey(node1.getMapString())) // 判断hash2是否存在该状态
                return node1.getCount() + hash1.get(node1.getMapString());
            int x = node1.getX();
            int y = node1.getY();
            for (int i = 0; i < 4; i++) {
                int tx = x + dir[i][0];
                int ty = y + dir[i][1];
                if (!check(tx, ty))
                    continue;
                char[][] t = node1.getCopyMap();
                t[x][y] = t[tx][ty];
                t[tx][ty] = '.';
                Node n = new Node(t, tx, ty, node1.getCount() + 1);
                String s = n.getMapString();
                if (hash2.containsKey(s))
                    return n.getCount() + hash2.get(s);
                if (!hash1.containsKey(s)) {
                    hash1.put(s, n.getCount());
                    queue1.offer(n);
                }
            }
        }
        if (!queue2.isEmpty()) {
            Node node2 = queue2.poll(); // 出列
            if (hash1.containsKey(node2.getMapString())) // 判断hash1是否存在该状态
                return node2.getCount() + hash1.get(node2.getMapString());
            int x = node2.getX();
            int y = node2.getY();
            for (int i = 0; i < 4; i++) {
                int tx = x + dir[i][0];
                int ty = y + dir[i][1];
                if (!check(tx, ty))
                    continue;
                char[][] t = node2.getCopyMap();
                t[x][y] = t[tx][ty];
                t[tx][ty] = '.';
                Node n = new Node(t, tx, ty, node2.getCount() + 1);
                String s = n.getMapString();
                if (hash1.containsKey(s))
                    return n.getCount() + hash1.get(s);
                if (!hash2.containsKey(s)) {
                    hash2.put(s, n.getCount());
                    queue2.offer(n);
                }
            }
        }
    }
    return -1;

    }

}

class Node {
char[][] map = new char[3][3];
int x, y, count;

public Node(char[][] map, int x, int y, int count) {
    super();
    this.map = map;
    this.x = x;
    this.y = y;
    this.count = count;
}

// 字符数组转成字符串
public String getMapString() {
    StringBuffer sb = new StringBuffer("");
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            sb.append(map[i][j]);
    return sb.toString();
}

public char[][] getCopyMap() {
    char copy[][] = new char[3][3];
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            copy[i][j] = map[i][j];
    return copy;
}

public char[][] getMap() {
    return map;
}

public void setCMap(char[][] map) {
    this.map = map;
}

public int getX() {
    return x;
}

public void setX(int x) {
    this.x = x;
}

public int getY() {
    return y;
}

public void setY(int y) {
    this.y = y;
}

public int getCount() {
    return count;
}

public void setCount(int count) {
    this.count = count;
}

}

测试数据

起始状态 目标状态 步数 BFS+Hash BFS双搜
12345678. 123.46758 3 0ms 0ms
13524678. 46758123. 22 119ms 19ms
12345678. 152743.86 6 0ms 0ms
.14276385 12345678. 26 240ms 41ms

20步以上的差距明显…

康托展开

康托展开

1464186837

摘要:八数码之状态标记
康托展开

康托展开的公式是 X=an(n-1)!+an-1(n-2)!+…+ai(i-1)!+…+a21!+a1*0! 其中,ai为当前未出现的元素中是排在第几个(从0开始)。

这个公式可能看着让人头大,最好举个例子来说明一下。例如,有一个数组 s = [“A”, “B”, “C”, “D”],它的一个排列 s1 = [“D”, “B”, “A”, “C”],现在要把 s1 映射成 X。n 指的是数组的长度,也就是4,所以

X(s1) = a43! + a32! + a21! + a10!

关键问题是 a4、a3、a2 和 a1 等于啥?

a4 = “D” 这个元素在子数组 [“D”, “B”, “A”, “C”] 中是第几大的元素。”A”是第0大的元素,”B”是第1大的元素,”C” 是第2大的元素,”D”是第3大的元素,所以 a4 = 3。

a3 = “B” 这个元素在子数组 [“B”, “A”, “C”] 中是第几大的元素。”A”是第0大的元素,”B”是第1大的元素,”C” 是第2大的元素,所以 a3 = 1。

a2 = “A” 这个元素在子数组 [“A”, “C”] 中是第几大的元素。”A”是第0大的元素,”C”是第1大的元素,所以 a2 = 0。

a1 = “C” 这个元素在子数组 [“C”] 中是第几大的元素。”C” 是第0大的元素,所以 a1 = 0。(因为子数组只有1个元素,所以a1总是为0)

所以,X(s1) = 33! + 12! + 01! + 00! = 20

A B C | 0
A C B | 1
B A C | 2
B C A | 3
C A B | 4
C B A | 5

上文摘自http://blog.csdn.net/zhongkeli/article/details/6966805
相关题目

标题:排列序数

如果用a b c d这4个字母组成一个串,有4!=24种,如果把它们排个序,每个串都对应一个序号:

abcd 0
abdc 1
acbd 2
acdb 3
adbc 4
adcb 5
bacd 6
badc 7
bcad 8
bcda 9
bdac 10
bdca 11
cabd 12
cadb 13
cbad 14
cbda 15
cdab 16
cdba 17

现在有不多于10个两两不同的小写字母,给出它们组成的串,你能求出该串在所有排列中的序号吗?

【输入格式】

一行,一个串。

【输出格式】

一行,一个整数,表示该串在其字母所有排列生成的串中的序号。注意:最小的序号是0。

例如:

输入:

bdca

程序应该输出:

11

再例如:

输入:

cedab

程序应该输出:

70

代码实现

import java.util.Scanner;

public class Aj {

static int[] hash = new int[10];

static int f(String string) {
    int length = string.length();
    int sum = 0;
    char[] ch = string.toCharArray();
    for (int i = 0; i < length; i++) {
        int k = 0;
        for (int j = i + 1; j < length; j++)
            if (ch[i] > ch[j]) // 计算当前是第几大
                k++;
        sum += hash[length - i - 1] * k; // j = length-i-1
    }
    return sum;
}

public static void main(String[] args) {
    hash[0] = 1;
    for (int i = 1; i < 10; i++)
        hash[i] = hash[i - 1] * i;
    Scanner scanner = new Scanner(System.in);
    String string = scanner.nextLine();
    System.out.println(f(string));
    scanner.close();
}

}