持续更新蓝桥杯算法训练题解,有兴趣可以关注一波呀。
车的放置
题目类似于八皇后问题,但是也有不同。由于是放置的棋子是车,所以只要求棋子不在同一行同一列即可,不需要对角线。另外没有规定棋子的个数,所以需要枚举放置棋子的个数是多少,然后转换成求八皇后的问题。
题目中的数据量很小,可以直接采用暴力的方法,时间复杂度 O ( n n ) O(n^n) O(nn)
暴力的思路是,对于每一行,选择没有棋子的一列放置一枚棋子,或者不放棋子。
这里有采用了两种方式:
- 按照题目描述,枚举放置1,2…,n个棋子进行求解。
- 直接遍历棋盘,如果放置了一个棋子,就直接把答案加1。
但是还有时间复杂度更低的做法,就是dp。时间复杂度 O ( n 4 ) O(n^4) O(n4),可以解决100以内的n。
时间复杂度的证明如下:
1 ∗ 1 ∗ n + 2 ∗ 2 ∗ n + 3 ∗ 3 ∗ n + 4 ∗ 4 + . . . n 3 = ( 1 2 + 2 2 + 3 2 + . . . n 2 ) ∗ n = n ∑ k = 1 n k 2 1 * 1 * n + 2 * 2 * n + 3 * 3 * n + 4 * 4 + ... n^ 3 = (1^2 + 2^2 + 3^2 +...n^2) * n = n\sum_{k=1}^nk^2 1∗1∗n+2∗2∗n+3∗3∗n+4∗4+...n3=(12+22+32+...n2)∗n=n∑k=1nk2~ n 4 n^4 n4
可以使用dp的原因是,可以发现,对于每一行来说,有几个选择,只和前面放置了多少个车以及总共有多少个车有关。比如总共有5个车,前面行放置了2个车,那么现在的行就有3个列可以选择。由于车是相同的,所以只需要计算车放在随便一列的个数,然后乘以3就是当行的答案了。
暴力方法0:
- 最初的暴力方法,枚举每一个位置,而不是枚举每一行,可以发现完全没有必要枚举每一行,因为只能向下走。而不放已经代表了这一种情况。
public static int dfs (int[] l, int cur, int curh) {
if (cur == m) {
return 1;
}
int ans = 0;
for (int i = curh; i < n; i++) {
for (int j = 0; j < n; j++) {
if (l[j] == 0) {
l[j] = 1;
ans += dfs(l, cur + 1, i + 1);
l[j] = 0;
}
}
}
return ans;
}
暴力方法1:
public static int dfs (int[] l, int cur, int curh) {
// l[i]判断第i列是否放置了棋子, cur已经放置的棋子个数,curh当前的行数
if (cur == m) {
return 1;
}
int ans = 0;
for (int j = 0; j < n; j++) {
if (l[j] == 0) {
l[j] = 1;
ans += dfs(l, cur + 1, curh, + 1);
l[j] = 0;
}
}
return ans;
}
暴力方法2:
private static int res = 1;
public static void dfs2(int curl, int[] visc) {
if (curl == n) return ;
// 在curh + 1行上面,从这些行中选出一个可以放的放上
for (int j = 0; j < n; j ++) {
if (visc[j] == 0) {
visc[j] = 1;
// 放上,答案就会多一个
res ++;
dfs2(curl + 1, visc);
visc[j] = 0;
}
}
// 不放
dfs2(curl + 1, visc);
}
dp方法:
private static int[][][]dp;
public static int dfs(int curh, int cur) {
if (cur == m) return 1;
if (curh >= n) return 0;
int tmp = dp[m][curh][cur];
if (tmp >= 0) return tmp;
tmp = 0;
// 啥都不放
tmp += dfs(curh + 1, cur);
// 总共m个,前面放了cur个,还有(n - cur)个位置可以放
tmp += dfs(curh + 1, cur + 1) * (n- cur);
dp[m][curh][cur] = tmp;
return tmp;
}
AC代码:
package lanqiao.imporve;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Scanner;
/**
* @author: Zekun Fu
* @date: 2022/10/13 9:59
* @Description: 车的放置
*
* 八皇后问题简化版:不能在同一行,同一列中
*/
public class CheDeFangZhi {
private static int n;
private static int m;
// 为了放置重复,每一个都从下一行开始放置
public static int dfs (int[] l, int cur, int curh) {
if (cur == m) {
return 1;
}
int ans = 0;
for (int i = curh; i < n; i++) {
for (int j = 0; j < n; j++) {
if (l[j] == 0) {
l[j] = 1;
ans += dfs(l, cur + 1, i + 1);
l[j] = 0;
}
}
}
return ans;
}
public static int[][][]dp;
public static int dfs(int curh, int cur) {
if (cur == m) return 1;
if (curh >= n) return 0;
int tmp = dp[m][curh][cur];
if (tmp >= 0) return tmp;
tmp = 0;
// 啥都不放
tmp += dfs(curh + 1, cur); // 啥都不放
// 总共m个,前面放了cur个,还有(n - cur)个位置可以放
tmp += dfs(curh + 1, cur + 1) * (n- cur);
dp[m][curh][cur] = tmp;
return tmp;
}
private static int res = 1;
public static void dfs2(int curl, int[] visc) {
if (curl == n) return ;
// 在curh + 1行上面,从这些行中选出一个可以放的放上
for (int j = 0; j < n; j ++) {
if (visc[j] == 0) {
visc[j] = 1;
// 放上,答案就会多一个
res ++;
dfs2(curl + 1, visc);
visc[j] = 0;
}
}
// 不放
dfs2(curl + 1, visc);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
// 方法1:
// int ans = 0;
// for (int i = 0; i <= n; i++) {
// int[] l = new int[n];
// m = i;
// ans += dfs(l, 0, 0);
// }
// 方法2
// int[]vis = new int[n];
// dfs2(0, vis);
// System.out.println(res);
// dp
int ans = 0;
dp = new int[(n + 1)][(n + 1)][(n + 1)];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
Arrays.fill(dp[i][j], -1);
}
}
for (int i = 0; i <= n; i++) {
m = i;
ans += dfs(0, 0);
}
System.out.println(ans);
}
}
/*
*
* dp[n][i] = dp[n - 1][i - 1] * n (i >= 2)
*
* 1
* 3 * 3 = 9
* 3 * 4 = 12 + 3 * 2 = 18
* 3 * 2 = 6
* 10 + 18 = 28 + 6 = 34
*
* 也就是说少了dp[n][m][curh] 这种情况
*
* 递归正确,递推出错。md
*
* */