目录
题目链接:8.摆放汤圆【算法赛】 - 蓝桥云课
注:下述题目描述和示例均来自蓝桥云客
题目描述
元宵节到了,老王家的汤圆摊前热闹非凡,顾客们排着长队等着品尝美味的汤圆。老王一边忙活,一边琢磨着怎么把汤圆摆得既好看又吉利。
老王家的汤圆盘是一个正方形的盘子,上面有 n×n 个格子,整齐地排列成 n 行 n 列的方阵。为了让顾客们眼前一亮,老王决定在盘子上摆放 n 个大汤圆。
不过,老王这人有点强迫症,摆汤圆得遵守几条规矩:
- 同一列的格子里不能放两个汤圆,不然会挤到。
- 同一行的格子里也不能放两个汤圆,不然会滚走。
- 最重要的是,为了体现元宵节的对称美,汤圆的摆放必须关于对角线(从左上到右下的直线)对称。对角线上的格子本身就满足对称性。
老王想了半天,也没算出来到底有多少种摆法。他挠了挠头,决定找你帮忙。现在,请你帮老王算算,满足这些条件的汤圆摆放方案数一共有多少种?由于答案可能很大,你只需要输出方案数对 取模后的结果即可。
数学发现——对合排列
推导过程
1. 分类讨论法
考虑构造一个 n 元素的对合排列,我们可以按第 n 个元素是否是固定点来分类讨论:
情况 1:第 n 个元素是固定点
此时,第 n 个元素不动,剩下的 n−1 个元素必须形成一个对合排列。因此,这种情况的方案数为:
情况 2:第 n 个元素与某个其他元素 i(1≤i≤n−1)形成对换
此时,第 n 个元素与第 i 个元素互换位置,剩下的 n−2 个元素必须形成一个对合排列。
- 选择 i 的方式有 n−1 种(因为 i 可以是 1,2,…,n−1 中的任意一个)。
- 剩下的 n−2 个元素的对合排列数为 C(n−2)。
所以,这种情况的方案数为:
总方案数:将两种情况相加,得到递推公式:
解决思路
汤圆摆放的问题,我们需要计算满足特定条件的排列数目。具体来说,汤圆必须满足以下条件:
- 每行每列恰好有一个汤圆。
- 摆放关于主对角线对称。
方法与步骤
-
问题分析:
- 每个汤圆的位置
(i, j)
必须与(j, i)
同时存在,因此排列必须是对称的。 - 这种排列称为对合排列(involution),即排列的平方等于恒等排列,所有循环长度为1或2。
- 每个汤圆的位置
-
递推关系:
- 对合排列的数目
C(n)
满足递推公式: - 初始条件:
C(0) = 1
,C(1) = 1
。
- 对合排列的数目
-
动态规划优化:
- 使用变量
a
和b
分别保存前两个值,逐步计算到n
,避免使用数组节省空间。
- 使用变量
解法一:动态规划
Java写法:
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
long t = scan.nextLong();
//直接计算到n的值
long[] dp = new long[1000001];
// 初始化n=0,1
dp[0] = 1;
dp[1] = 1;
for(int n = 2;n <= 1000000;n++){
dp[n] = (dp[n - 1] + (n - 1)*dp[n - 2]) % 1000000007;
}
for(int i = 0;i < t;i++){
int N = scan.nextInt();
System.out.println(dp[N]);
}
scan.close();
}
}
C++写法:
#include <iostream>
#include <vector>
using namespace std;
const int MOD = 1e9 + 7;
const int MAXN = 1000000;
int main() {
int t;
cin >> t;
// 使用 vector 避免栈溢出
vector<long long> dp(MAXN + 1, 0);
dp[0] = 1; // C(0) = 1
dp[1] = 1; // C(1) = 1
for (int n = 2; n <= MAXN; ++n) {
dp[n] = (dp[n-1] + (long long)(n-1) * dp[n-2]) % MOD;
}
while (t--) {
int N;
cin >> N;
cout << dp[N] % MOD << endl;
}
return 0;
}
AC情况
时间复杂度和空间复杂度
- 时间复杂度: O(t)O(t)
- 空间复杂度: O(N)O(N)
总结
很难,要懂数学知识或者是你能现场推算出来,抱歉,这里我没有推出来,我查资料了。