2022.10.20 总结
1. 逐月 P5049
题意
有一个 \(n \times n\) 的矩阵,现在还有 \(k\) 个 \(n \times n\) 的碎片,需要把两个碎片拼成这个矩阵,并且两个碎片只能水平或者垂直移动。
两块碎片中的 \(#\) 号不能重叠,也不能超出矩阵范围。
请你求出这个矩阵是由哪两个碎片组成的,答案唯一。
思路
100 分
因为只能水平和垂直移动,所以可以直接枚举两个碎片水平和垂直移动多少,然后暴力拼接两个碎片,检查是否和矩阵相同即可。
时间复杂度
枚举两个碎片,\(O(k ^ 2)\)。
枚举水平和垂直的偏移量,\(O(n ^ 4)\)。
拼接碎片和检查矩阵,\(O(n ^ 2)\)。
总时间复杂度为 \(O(k ^ 2 \times n ^ 6)\)。
空间复杂度
记录原本矩阵,\(O(n ^ 2)\)。
记录碎片,\(O(k \times n ^ 2)\)。
临时拼接碎片,\(O(n ^ 2)\)。
总时间复杂度为 \(O(k \times n ^ 2)\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 10, K = 15;
int n, k, cnt;
bool h[N][N], p[K][N][N], f[N][N];
bool F(int id, int d1, int d2){ // 拼接
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
int nx = i + d1, ny = j + d2;
if (min(nx, ny) >= 1 && max(nx, ny) <= n && p[id][i][j] && f[nx][ny]) {
return 0;
}
if (nx < 1 || ny < 1 || nx > n || ny > n || f[nx][ny]) {
continue;
}
f[nx][ny] = p[id][i][j];
}
}
return 1;
}
bool P(){ // 检查
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (h[i][j] != f[i][j]) {
return 0;
}
}
}
return 1;
}
void cl(){ // 清空
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = 0;
}
}
}
int main(){
freopen("bcs.in", "r", stdin);
freopen("bcs.out", "w", stdout);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
char v;
cin >> v;
h[i][j] = (v == '#');
}
}
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
char v;
cin >> v;
p[i][j][k] = (v == '#');
}
}
}
for (int i = 1; i <= k; i++) {
for (int j = i + 1; j <= k; j++) {
for (int a = 1 - n; a <= n - 1; a++) {
for (int b = 1 - n; b <= n - 1; b++) {
for (int c = 1 - n; c <= n - 1; c++) {
for (int d = 1 - n; d <= n - 1; d++) {
if (F(i, a, b) && F(j, c, d) && P()) {
cout << i << ' ' << j;
return 0;
}
cl();
}
}
}
}
}
}
return 0;
}
2. 逐月 P5063
题意
有一个 \(n \times n\) 的矩阵,Bessie 要从 \((1, 1)\) 走到 \((n, n)\)。
请问有多少种不同的回文路径能让 Bessie 走到终点。
思路
36 分到 44 分
暴搜路径,判断是否为回文串以及是否之前出现过。
时间复杂度
回文路径长度最多为 \(2n - 1\),每个字符都可以往下走和往右走,\(O(2 ^ {2n})\)。
检查是否出现过,最多 \(O(2 ^ {2n})\)。
总时间复杂度为 \(O(2 ^ {2n})\)。
空间复杂度
每次记录长度为 \(2n\) 的字符串,最多有 \(2 ^ n\) 种字符串,总空间复杂度为 \(O(n ^ {2n})\)。
84 分
因为是回文路径,所以只要知道前半段就能求出后半段。
而每条路径的长度肯定是 \(2n - 1\),所以只要搜到 \(n\) 个字符就往后找后半段。
时间复杂度
枚举前半段,\(O(2 ^ n)\)。
根据前半段找出后半段,\(O(2 ^ n)\)。
检查是否出现过,\(O(2 ^ {2n})\)。
总时间复杂度为 \(O(2 ^ {2n})\)。
因为找路径的时间复杂度会变少,所以会比暴搜更快。
空间复杂度
每次记录长度为 \(2n\) 的字符串,最多有 \(2 ^ n\) 种字符串,总空间复杂度为 \(O(n ^ {2n})\)。
100 分
因为检查是否出现过用的时间过多,所以可以用 \(map\) 或者 \(set\) 优化。
时间复杂度
枚举前半段,\(O(2 ^ n)\)。
根据前半段找出后半段,\(O(2 ^ n)\)。
检查是否出现过,\(O(\log cnt)\) (\(cnt\) 是目前有的回文路径的数量)。
总时间复杂度为 \(O(2 ^ {n} \times \log cnt)\)。
空间复杂度
每次记录长度为 \(2n\) 的字符串,最多有 \(2 ^ n\) 种字符串,总空间复杂度为 \(O(n ^ {2n})\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n, ct;
char c[N][N];
string s;
set<string> t;
bool F(string s){ // 是否存在过
if (t.find(s) != t.end()) { // 如果没有,会返回 .end()
return 0;
}
return 1;
}
bool P(int x, int y, int num){ // 是否可以搜到后半段
if (x > n || y > n || c[x][y] != s[num]) {
return 0;
}
return num == 1 || P(x + 1, y, num - 1) || P(x, y + 1, num - 1);
}
void dfs(int x, int y, int cnt){
if (x < 1 || x > n || y < 1 || y > n) {
return ;
}
s[cnt] = c[x][y];
if (cnt == n) {
if (P(x, y, n) && F(s)) {
t.insert(s);
ct++;
}
return ;
}
dfs(x, y + 1, cnt + 1);
dfs(x + 1, y, cnt + 1);
}
int main(){
freopen("palpath.in", "r", stdin);
freopen("palpath.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> c[i][j];
}
}
s.resize(n + 1);
dfs(1, 1, 1);
cout << ct;
return 0;
}
也可以记录所有回文字符串,最后排序去重。