放牛问题:
假设你家有n头牛(体重各不相同),但是这牛是薛定谔的牛,你不知道它具体是什么颜色,只有牵出来才知道,颜色有m种。然后问你一共有多少种不同的放牛方法。也就是说你可以拉出多少不同状态的牛,只有当牛的数量、体重和颜色完全一样时才视为同一个状态。注意:因为可能结果很大,因此需要模1000000007
.
例如:n=3, m=2。
你可以选择一头牛也不牵出来,此时只有1种状态;
你也可以牵出1头牛,则有 C 3 1 C_3^1 C31种可能,牛的颜色可能有2种,此时一共6种状态;
你可以牵出2头牛,则有 C 3 2 C_3^2 C32种可能,牛的颜色可能有 2 2 2^2 22种,此时有12种状态;
你可以牵出3头牛,有1种可能,牛的颜色有 2 3 2^3 23此时有8种状态;
最终加起来一共是27种状态。
解题历程:
首先就是按照题中所给的例子的方法,先计算组合数,然后计算颜色的种类,两者相乘得到状态数,最后相加得到总数。但是这种方法超时。
然后开始改进,突然发现这其实就是二项式定理,结果应该为 ( m + 1 ) n (m+1)^n (m+1)n,但是提交发现溢出,尽管已经对结果求模了。
继续改进,将所有的变量都改为long型,这次结果不溢出了,但是还是超时。
再次改进,使用快速幂方法。时间复杂度一下子降到了log(n)。
代码实现:
/**
* 计算幂,快速幂
* @param base 底
* @param n 指数
* @return base的n次幂
* 时间复杂度为logn
*/
public static long pow(long base, long n) {
if (n == 0) return 1;
long ans = 1;
long b = base;
while (n != 0) {
//n不等于0时继续
if ((n & 1) != 0) ans = (ans * b) % MOD;//末尾为1,就乘以对应的mi,否则乘1
//幂指增加
b = (b * b) % MOD;
//右移一位
n = n >> 1;
}
return ans;
}
问题描述:
有一块网格区域,其中遍布着海洋与陆地,每块格子代表一块陆地或者是海洋,如果从海洋到相邻的陆地或者从陆地到相邻的海洋,需要花费5点体力,如果是从海洋到另一个海洋则需要3点体力,陆地到陆地需要2点体力。给定这个区域的布局,以及起点和终点坐标,求出从起点到终点要消耗的最小体力值。
思路:
有两种做法,深度优先(广度优先)和迪杰斯特拉,本来想试试动态规划,发现并不适用。
代码:
import java.util.Scanner;
/**
* @author FANG
* @version 1.8
* @coding UTF-8
* @date 2020/7/31 19:06
*/
public class One {
private final static long MOD = 1000000007;
private static int min_force = 0x7fffffff;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// while (in.hasNext()) {
// long n = in.nextLong();
// long m = in.nextLong() + 1;
// System.out.println(pow(m, n));
// }
// in.nextByte();
// int n = in.nextInt();//行数
// int m = in.nextInt();//列数
// int num = in.nextInt();//测试例数目
int n = 4, m = 4, num = 4;
char[][] land = new char[][]{
{
'C','C','C','S'
},
{
'S','S','S','S'
},
{
'C','S','C','S'
},
{
'S','S','C','C'
}
};
// for (int i = 0; i < n; i++) {
// land[i] = in.next().toCharArray();
// }
boolean[][] visited = new boolean[n][m];
for (int i = 0; i < num; i++) {
int startR = in.nextInt()-1;//起点行号
int startC = in.nextInt()-1;//起点列号
int endR = in.nextInt()-1;//终点行号
int endC = in.nextInt()-1;//终点列号
dfs(land, visited, startR, startC, endR, endC, 0);
System.out.println(min_force);
min_force = 0x7fffffff;
System.out.println(minForce(land, startR, startC, endR, endC));
}
}
/**
* 计算幂,快速幂
* @param base 底
* @param n 指数
* @return base的n次幂
* 时间复杂度为logn
*/
public static long pow(long base, long n) {
if (n == 0) return 1;
long ans = 1;
long b = base;
while (n != 0) {
//n不等于0时继续
if ((n & 1) != 0) ans = (ans * b) % MOD;//末尾为1,就乘以对应的mi,否则乘1
//幂指增加
b = (b * b) % MOD;
//右移一位
n = n >> 1;
}
return ans;
}
/**
* 使用深度优先搜索地图找到花费体力最小的路径
* @param land 地形图
* @param curi 起点行号
* @param curj 起点列号
* @param ei 终点行号
* @param ej 终点列号
* @param cur_force 走到当前点消耗的体力
*/
public static void dfs(char[][] land, boolean[][] visited, int curi, int curj, int ei, int ej, int cur_force) {
//走到了终点
if (curi == ei && curj == ej) {
//走到了终点
//更新最小体力值
// System.out.println(cur_force);
min_force = Math.min(min_force, cur_force);
return;
}
//如果当前体力消耗已经大于等于当前最小值,则可以不用走了
if (cur_force >= min_force) return;
char ch = land[curi][curj];
//向上走
if (curi - 1 >= 0 && !visited[curi-1][curj]) {
//要保证不能越界,并且未被走过
//
visited[curi-1][curj] = true;
char c = land[curi-1][curj];
int cost;
if (ch != c) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//开始深度优先搜索
dfs(land, visited, curi-1, curj, ei, ej, cur_force + cost);
//恢复状态
visited[curi-1][curj] = false;
}
//向下走
if (curi + 1 < land.length && !visited[curi+1][curj]) {
//要保证不能越界,并且未被走过
//
visited[curi+1][curj] = true;
char c = land[curi+1][curj];
int cost;
if (ch != c) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//开始深度优先搜索
dfs(land, visited, curi+1, curj, ei, ej, cur_force + cost);
//恢复状态
visited[curi+1][curj] = false;
}
//向左走
if (curj - 1 >= 0 && !visited[curi][curj-1]) {
//要保证不能越界,并且未被走过
//
visited[curi][curj-1] = true;
char c = land[curi][curj-1];
int cost;
if (ch != c) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//开始深度优先搜索
dfs(land, visited, curi, curj-1, ei, ej, cur_force + cost);
//恢复状态
visited[curi][curj-1] = false;
}
//向左走
if (curj + 1 < land[0].length && !visited[curi][curj+1]) {
//要保证不能越界,并且未被走过
//
visited[curi][curj+1] = true;
char c = land[curi][curj+1];
int cost;
if (ch != c) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//开始深度优先搜索
dfs(land, visited, curi, curj+1, ei, ej, cur_force + cost);
//恢复状态
visited[curi][curj+1] = false;
}
}
/**
* 使用迪杰斯特拉最短路算法求解
* @param land 海陆分布图
* @param si 起点坐标
* @param sj 起点坐标
* @param ei 终点坐标
* @param ej 终点坐标
* @return 最小体力值
*/
public static int minForce(char[][] land, int si, int sj, int ei, int ej) {
int n = land.length;
int m = land[0].length;
//初始化最小体力数组,minF[i][j]表示从起点到该点花的最小体力
int[][] minF = new int[n][m];
boolean[][] visited = new boolean[n][m];
visited[si][sj] = true;
//初始化minF数组
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
//赋初值,正无穷大
minF[i][j] = Integer.MAX_VALUE;
//如果是它自己,则改为0
if (i == si && j == sj) minF[i][j] = 0;
//上邻居
if (i == si-1 && j == sj) {
char neib = land[si][sj];
char ch = land[i][j];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
minF[i][j] = cost;
}
// 下邻居
if (i == si+1 && j == sj) {
char neib = land[si][sj];
char ch = land[i][j];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
minF[i][j] = cost;
}
//左邻居
if (i == si && j == sj-1) {
char neib = land[si][sj];
char ch = land[i][j];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
minF[i][j] = cost;
}
// 右邻居
if (i == si && j == sj+1) {
char neib = land[si][sj];
char ch = land[i][j];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
minF[i][j] = cost;
}
}
}
//开始迭代找次短路径(次小体力消耗路径)
for (int k = 1; k < n*m; k++) {
//找未访问集合中路径最短的
int min = Integer.MAX_VALUE;
int min_i = -1, min_j = -1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
//未访问过结点中最短的
if (!visited[i][j]) {
// System.out.println(minF[i][j]);
if (minF[i][j] < min) {
min = minF[i][j];
min_i = i;
min_j = j;
// System.out.println(min);
}
}
}
}
//找到后添加进访问集合中
visited[min_i][min_j] = true;
//然后用这个点去更新未被添加进访问集合中的其他点,
// 因为只有它的邻居可能被更新,所以只需要看它的邻居即可。
//上邻居存在且不在最短路集合中
if (min_i - 1 >= 0 && !visited[min_i-1][min_j]) {
char ch = land[min_i][min_j];
char neib = land[min_i-1][min_j];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//更新其值
minF[min_i-1][min_j] = Math.min(minF[min_i-1][min_j], minF[min_i][min_j] + cost);
}
//下邻居存在且不在最短路集合中
if (min_i + 1 < n && !visited[min_i+1][min_j]) {
char ch = land[min_i][min_j];
char neib = land[min_i+1][min_j];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//更新其值
minF[min_i+1][min_j] = Math.min(minF[min_i+1][min_j], minF[min_i][min_j] + cost);
}
//左邻居存在且不在最短路集合中
if (min_j - 1 >= 0 && !visited[min_i][min_j-1]) {
char ch = land[min_i][min_j];
char neib = land[min_i][min_j-1];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//更新其值
minF[min_i][min_j-1] = Math.min(minF[min_i][min_j-1], minF[min_i][min_j] + cost);
}
//右邻居存在且不在最短路集合中
if (min_j + 1 < m && !visited[min_i][min_j+1]) {
char ch = land[min_i][min_j];
char neib = land[min_i][min_j+1];
int cost;
if (ch != neib) cost = 5;
else if (ch == 'C') cost = 2;
else cost = 3;
//更新其值
minF[min_i][min_j+1] = Math.min(minF[min_i][min_j+1], minF[min_i][min_j] + cost);
}
}
return minF[ei][ej];
}
}