洛谷-P14331 [JOI2021 预选赛 R2] 煎饼 / Pancake
洛谷-P14331 [JOI2021 预选赛 R2] 煎饼 / Pancake
P14331 [JOI2021 预选赛 R2] 煎饼 / Pancake - 洛谷
用 \(N\) 张煎饼堆叠成煎饼塔,煎饼共有三种口味,分别称为 A、B、C。
将满足以下条件的煎饼塔称为“良好煎饼塔”:
- 对于任意一张口味 A 的煎饼与一张口味 B 的煎饼,口味 A 的煎饼必须位于口味 B 的煎饼之上。
- 对于任意一张口味 A 的煎饼与一张口味 C 的煎饼,口味 A 的煎饼必须位于口味 C 的煎饼之上。
- 对于任意一张口味 B 的煎饼与一张口味 C 的煎饼,口味 B 的煎饼必须位于口味 C 的煎饼之上。
可以对煎饼塔执行以下操作:
- 操作 \(k\):反转从顶部数起前 \(k\) 张煎饼的排列顺序。
现共有 \(Q\) 座煎饼塔,第 \(i\) 座煎饼塔(\(1 \le i \le Q\))从上至下口味序列为 \(S_{i,1} S_{i,2} \cdots S_{i,N}\)。
比太郎希望对每一座煎饼塔,用尽可能少的操作次数将其变为良好煎饼塔。
给定 \(Q\) 座煎饼塔的口味排列信息,请编写程序,求出每座煎饼塔变为良好煎饼塔所需的最少操作次数。
\(2 \le N \le 13\),\(1 \le Q \le 100\,000\)。
三进制状态压缩。将每一种口味的排列映射为一个三进制数,进而用一个节点表示。
如果对一种状态进行一次操作,可以转换为另一种状态,那么就在这两个状态对应的节点之间连边。
那么,所要求的就是从给定状态到任意“良好状态”的最短路径。
由于目标良好状态是已知的,而起点有 \(Q\) 个是不固定的,因此我们不妨反过来,预处理出从任意目标状态变成任意其他状态所需的最少次数(在图中即是经过的最少边数)。这样,不论询问以哪一个状态为起点,都可以 \(O(1)\) 回答。
边权全为 \(1\),故采用多源 BFS 进行预处理。时间复杂度 \(O(N\cdot3^N+Q)\)。
(套路类似的题:Gym-105578 Light Up the Grid。)
#include <bits/stdc++.h>
using namespace std;
int const N = 1.6e6;
int n, Q, dis[N];
int to_ternary(string s) {
int res = 0;
for (char c: s)
res = res * 3 + (c - 'A');
return res;
}
void from_ternary(int x, string &s) {
s.resize(n);
for (int i = n - 1; i >= 0; i--) {
s[i] = (x % 3) + 'A';
x /= 3;
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> Q;
memset(dis, -1, sizeof dis);
queue<int> q;
for (int i = 0; i <= n; i++) {
for (int j = 0; i + j <= n; j++) {
int k = n - i - j;
string s;
for (int x = 0; x < i; x++) s += 'A';
for (int x = 0; x < j; x++) s += 'B';
for (int x = 0; x < k; x++) s += 'C';
int u = to_ternary(s);
dis[u] = 0;
q.push(u);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
string s;
from_ternary(u, s);
for (int k = 2; k <= n; k++) {
reverse(s.begin(), s.begin() + k);
int v = to_ternary(s);
if (dis[v] == -1) {
dis[v] = dis[u] + 1;
q.push(v);
}
reverse(s.begin(), s.begin() + k);
}
}
while (Q--) {
string s;
cin >> s;
int u = to_ternary(s);
if (dis[u] == -1) cout << "-1\n";
else cout << dis[u] << '\n';
}
return 0;
}

浙公网安备 33010602011771号