данный код не проходит time limit в 2 секунды. Ско...

Created on: May 19, 2025

Answered using O3 by Chat01

Question

данный код не проходит time limit в 2 секунды. Скорее всего здесь есть какая-то ошибка в реализации алгоритма. Помоги ее найти
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <unordered_map>
#include <climits>
#include <string>

using NextStrings = std::vector<std::pair<std::string, char>>;

#pragma pack(push, 1)
struct Gambit {
int neigh;
char dx;
Gambit(int n, char d) : neigh(n), dx(d) {
}
};
#pragma pack(pop)

class Graph {
private:
std::vector<std::vector<Gambit>> adj_;
static constexpr int kPermNum = 362880;
std::unordered_map<int, std::string> ind_state_;
std::unordered_map<std::string, int> state_ind_;
std::string asc_ = "012345678";
std::string target_ = "123456780";
std::string init_;

NextStrings DerivePossiblePermutations(const std::string& state) {
size_t zero_ind = state.find('0');
NextStrings next_strings;
if (zero_ind / 3 != 0) {
std::string next_state = state;
std::swap(next_state[zero_ind], next_state[zero_ind - 3]);
next_strings.emplace_back(next_state, 'U');
}
if (zero_ind / 3 != 2) {
std::string next_state = state;
std::swap(next_state[zero_ind], next_state[zero_ind + 3]);
next_strings.emplace_back(next_state, 'D');
}
if (zero_ind % 3 != 2) {
std::string next_state = state;
std::swap(next_state[zero_ind], next_state[zero_ind + 1]);
next_strings.emplace_back(next_state, 'R');
}
if (zero_ind % 3 != 0) {
std::string next_state = state;
std::swap(next_state[zero_ind], next_state[zero_ind - 1]);
next_strings.emplace_back(next_state, 'L');
}
return next_strings;
}

void BFS(std::vector<int>& dist, std::vector<int>& parent, std::vector<char>& parent_move) {
int init_ind = state_ind_.at(init_);
int target_ind = state_ind_.at(target_);
dist[init_ind] = 0;
std::queue<int> q;
q.emplace(init_ind);
while (!q.empty()) {
int v = q.front();
q.pop();
if (v == target_ind) {
break;
}
const std::string& cur_state = ind_state_.at(v);
NextStrings next_strings = DerivePossiblePermutations(cur_state);
for (const auto& next : next_strings) {
int neigh = state_ind_.at(next.first);
if (dist[neigh] == INT_MAX) {
dist[neigh] = dist[v] + 1;
parent[neigh] = v;
parent_move[neigh] = next.second;
q.emplace(neigh);
}
}
}
}

public:
explicit Graph(std::string s) : adj_(kPermNum), init_(std::move(s)) {
int ind = 0;
do {
state_ind_[asc_] = ind;
ind_state_[ind] = asc_;
++ind;
} while (std::next_permutation(asc_.begin(), asc_.end()));
}

void FindSolution() {
std::vector<int> dist(kPermNum, INT_MAX);
std::vector<int> parent(kPermNum, -1);
std::vector<char> parent_move(kPermNum, 0);
BFS(dist, parent, parent_move);
int target_ind = state_ind_.at(target_);
if (dist[target_ind] == INT_MAX) {
std::cout << -1;
return;
}
std::string cmd_seq;
for (int cur = target_ind; parent[cur] != -1; cur = parent[cur]) {
cmd_seq += parent_move[cur];
}
std::reverse(cmd_seq.begin(), cmd_seq.end());
std::cout << dist[target_ind] << '\n' << cmd_seq;
}
};

int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
std::string init;
for (int i = 0; i < 9; ++i) {
char c = 0;
std::cin >> c;
init += c;
}
Graph graph(init);
graph.FindSolution();
}

Thought

Analyzing algorithm time complexity

The user is discussing an algorithm that fails the 2-second time limit. They’re enumerating all 362,880 permutations of an 8-puzzle and precomputing state mappings using BFS. The complexity of O(362880 * 9 log??) seems reasonable, but the issue might lie in the map insertions and the next_permutation loops. Despite iterating over each permutation once, these operations could still be costly and push the algorithm beyond the time limit. I'll help analyze further.

Optimizing BFS algorithm

It seems like the biggest issue here is the use of unordered_map for state_ind_ and ind_state_ during BFS, which could be slower, especially when considering 180k states or more in the worst case. Computing all permutations through std::next_permutation and relying on an ascending string (asc_) might create issues since asc_ is mutated during processing. The BFS might also be inefficient due to the large state space (362k nodes), which could be optimized through prelabeled arrays or distance tables — but there may also be a bug causing solvable parity issues.

Optimizing algorithm performance

The user is concerned about the 2-second time limit for their 8-puzzle algorithm. Enumerating all permutations and performing BFS might exceed this. An optimization suggestion is to replace the unordered_map with precomputed arrays and use Cantor expansion for faster state encoding. Additionally, the adjacency list (adj_) is allocated but not used in BFS, leading to memory overhead. This unnecessary allocation might stress both memory and time limits. Investigating the BFS stops early, but heavy initialization loops could be the main cause of the performance issue.

Explaining algorithm inefficiencies

The main issue with the algorithm is precomputing all 362k permutations using next_permutation, which requires heavy memory usage and slow insertions into unordered_map. A better approach would be using Cantor encoding to compute indices on the fly, or relying on BFS only with visited states stored in the map for faster lookups. Alternatively, we can use A* with Manhattan heuristic for an faster solution. Additionally, removing the adjacency list and using arrays pegged with indices, instead of mappings, would save memory.

Thought for 1m 57s

Answer

Где «заседает» время

  1. Конструктор Graph

    cpp
    do { state_ind_[asc_] = ind; ind_state_[ind] = asc_; } while (std::next_permutation(asc_.begin(), asc_.end()));
    • 362 880 вызовов std::next_permutation.
    • Для каждой перестановки два вставления в unordered_map со строковым ключом (хэш-функция + динамическое выделение памяти для std::string).
      Это уже десятки миллионов мелких операций и сотни мегабайт аллокаций на горячем пути: за 2 с не успевает.
  2. Поиск в картах во время BFS
    Внутри цикла:

    cpp
    int neigh = state_ind_.at(next.first); // hash(string) каждую итерацию const std::string& cur_state = ind_state_.at(v);

    BFS для самой «дальней» начальной позиции посещает ≈ 180 000 вершин, т.е. ещё сотни тысяч обращений к тем же unordered_map + копирование строк-состояний.

  3. adj_ не используется, но инициализируется
    std::vector<std::vector<Gambit>> adj_(kPermNum) создает 362 880 пустых std::vector-ов — лишние ~9 МБ памяти и время на конструкторы.


Как починить за < 2 с

1. Избавиться от unordered_map и строк

Представим перестановку как число от 0 до 362 879 при помощи кодов Лемера (Cantor).

cpp
constexpr int fact[10] = {1,1,2,6,24,120,720,5040,40320,362880}; int encode(const std::array<char,9>& a) { // O(9^2) &asymp; 81 операций int res = 0; bool used[9] = {}; for (int i = 0; i < 9; ++i) { int cnt = 0; for (int d = 0; d < a[i]-'0'; ++d) if (!used[d]) ++cnt; res += cnt * fact[8-i]; used[a[i]-'0'] = true; } return res; }
  • encode не аллоцирует память, работает на массиве/строке фиксированной длины.
  • Обратное преобразование (decode) потребуется лишь для восстановления пути, не для BFS.

2. Хранить всё в массивах фиксированного размера

cpp
std::array<int, 362880> dist; std::array<int, 362880> parent; std::array<char,362880> parent_move;

Нулевые инциализации memset-ом ≈ 3 МБ.

3. BFS без промежуточных карт

  • В очереди лежат коды (int), а не строки.
  • Для генерации соседей достаточно позиции «нуля» в строке (её можно хранить вместе с кодом или быстро искать по массиву).

4. Убрать неиспользуемое

Удаляем adj_, ind_state_, state_ind_, Gambit.

5. (Опционально) двусторонний BFS или A*

Даже односторонний BFS с числами проходит < 0.1 с, но если хочется ещё запас — запустите BFS одновременно от старта и цели.


Итог: скелет исправленного решения

cpp
#include <bits/stdc++.h> using namespace std; constexpr int N = 362880; constexpr int fact[10] = {1,1,2,6,24,120,720,5040,40320,362880}; int encode(const array<char,9>& s) { /* как выше */ } array<char,9> decode(int code) { /* обратная операция */ } array<int,4> dx = { -3, +3, +1, -1 }; // U, D, R, L array<char,4> mv = { 'U','D','R','L' }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); array<char,9> start{}; for (char &c : start) cin >> c; array<char,9> goal = {'1','2','3','4','5','6','7','8','0'}; int sID = encode(start), gID = encode(goal); static int dist[N]; fill(begin(dist), end(dist), -1); static int par[N]; static char pMove[N]; queue<int> q; q.push(sID); dist[sID] = 0; while (!q.empty()) { int v = q.front(); q.pop(); if (v == gID) break; auto state = decode(v); int z = find(begin(state), end(state), '0') - begin(state); for (int k = 0; k < 4; ++k) { int nz = z + dx[k]; if (nz < 0 || nz >= 9 || (z%3==0&&k==3) || (z%3==2&&k==2)) continue; swap(state[z], state[nz]); int to = encode(state); if (dist[to] == -1) { dist[to] = dist[v]+1; par[to] = v; pMove[to]= mv[k]; q.push(to); } swap(state[z], state[nz]); // вернуть обратно } } if (dist[gID] == -1) { cout << -1; return 0; } string path; for (int cur = gID; cur != sID; cur = par[cur]) path += pMove[cur]; reverse(path.begin(), path.end()); cout << dist[gID] << '\n' << path << '\n'; }

- никаких unordered_map, ни одной аллокации во время работы – типичная реализация проходит < 0.05 с в g++-O2 и уверенно укладывается в лимит 2 с.


Главное, что «сломано» в исходном коде

ПроблемаПочему медленноКак исправить
Полный перебор next_permutation + две карты unordered_map<string, …>Сотни тысяч хэширований и аллокаций строкКантор-кодирование → один int
Доступ к картам на каждом шаге BFSЕщё сотни тысяч хэшированийРаботаем только с int
Гигантский (и неиспользуемый) adj_Лишняя инициализация 362 880 векторовУдалить

Исправив эти места, программа легко проходит TL.

Share this Q&A