Speed Up Bidirectional A* Algorithm

Chào mọi người, em lại gắp khó khăn trong khi làm bài Bidirectional A*, bài làm của em đã chạy đúng kết quả mà bị TLE: 64.00/50.00. Có lẽ, em cứ nghĩ mãi mà ko biết làm sao để speed up lên một tí nữa. Đây là đề bài:


Đây là ví dụ:

Ý tưởng bài giải là tính lại các trọng số của các đoạn thẳng thông qua hàm potential Function như sau:

Trong đó, piF là potential Function của Forward Graph, piR là potential Function của BackWard Graph.
Dùng hàm Potential Euclide như sau:

Khi đó, độ dài đoạn thẳng mới là:

Sau đó, áp dụng Bidirectional Dijsktra cho đoạn thẳng có trọng số mới:
Code của em:

#include "pch.h"
#include <iostream>
#include <cstdio>
#include <cassert>
#include <vector>
#include <queue>
#include <limits>
#include <utility>
#include <cmath>
using namespace std;
// See the explanations of these typedefs and constants in the starter for friend_suggestion
typedef vector<vector<vector<int>>> Adj;
typedef long long Len;
typedef vector<priority_queue<pair<Len, int>, vector<pair<Len, int>>, greater<pair<Len, int>>>> Queue;

const Len infinite = numeric_limits<Len>::max() / 4;

class AStar {
	// See the descriptions of these fields in the starter for friend_suggestion
	int n_;
	Adj adj_;
	Adj cost_;
	vector<vector<Len>> distance_;
	vector<vector<double>> distWithPotential_;
	vector<int> workset_;
	vector<bool> visited_;
	// Coordinates of the nodes
	vector<pair<Len, Len>> xy_;

public:
	AStar(int n, Adj adj, Adj cost, vector<pair<Len, Len>> xy)
		: n_(n), adj_(adj), cost_(cost), distance_(2, vector<Len>(n_, infinite)), distWithPotential_(2, vector<double>(n_, infinite)), visited_(n), xy_(xy)
	{
	}
	// See the description of this method in the starter for friend_suggestion
	void clear() {
		for (int i = 0; i < workset_.size(); ++i) {
			int v = workset_[i];
			distance_[0][v] = infinite;
			distance_[1][v] = infinite;
			distWithPotential_[0][v] = infinite;
			distWithPotential_[1][v] = infinite;
			visited_[v] = false;
		}
		workset_.clear();
	}

	double potentialFunction(pair<int, int> curr, pair<int, int> s, pair<int, int> t)
	{
		return (sqrt(pow(t.first - curr.first,2) + pow(t.second - curr.second,2)) - sqrt(pow(s.first - curr.first,2) + pow(s.second- curr.second,2))) / 2;
	}

	// See the description of this method in the starter for friend_suggestion
	void visit(Queue& q, int side, int v, double fakedist) {
		// Implement this method yourself
		if (distWithPotential_[side][v] == infinite)
		{
			q[side].push(make_pair(fakedist, v));
			workset_.push_back(v);
			return;
		}
		queue<pair<double, int>> tempQueue;
		while (!q[side].empty())
		{
			pair<double, int> curr = q[side].top();
			q[side].pop();
			if (curr.second == v)
			{
				curr.first = fakedist;
				q[side].push(curr);
				workset_.push_back(v);
				break;
			}
			tempQueue.push(curr);
		}
		while (!tempQueue.empty())
		{
			q[side].push(tempQueue.front());
			tempQueue.pop();
		}
	}
	// Returns the distance from s to t in the graph.
	Len query(int s, int t) {
		if (s == t) return 0;
		clear();
		Queue q(2);
		visit(q, 0, s, 0);
		visit(q, 1, t, 0);
		// Implement the rest of the algorithm yourself
		distance_[0][s] = 0;
		distance_[1][t] = 0;
		distWithPotential_[0][s] = 0;
		distWithPotential_[1][t] = 0;
		Len result = infinite;
		while (!q[0].empty() && !q[1].empty())
		{
			// dijsktra on forward graph
			pair<Len, int> currentFor = q[0].top();
			int current = currentFor.second;
			q[0].pop();
			for (int i = 0; i < adj_[0][currentFor.second].size(); i++)
			{
				int neighbour = adj_[0][current][i];
				double lWithPotetial = cost_[0][current][i] - potentialFunction(xy_[current], xy_[s], xy_[t]) + potentialFunction(xy_[neighbour], xy_[s], xy_[t]);
				double fakedist = lWithPotetial + distWithPotential_[0][current];
				if (fakedist < distWithPotential_[0][neighbour])
				{
					Len realdist = distance_[0][current] + cost_[0][current][i];
					visit(q, 0, neighbour, fakedist);
					distance_[0][neighbour] = realdist;
					distWithPotential_[0][neighbour] = fakedist;
				}
			}
			//update result
			if (result > distance_[0][current] + distance_[1][current])
				result = distance_[0][current] + distance_[1][current];
			// check if currentFor is visited
			if (visited_[current] == false)
				visited_[current] = true;
			else
				break;
			// dijsktra on reverse graph
			pair<Len, int> currentBack = q[1].top();
			q[1].pop();
			current = currentBack.second;
			for (int i = 0; i < adj_[1][current].size(); i++)
			{
				int neighbour = adj_[1][current][i];
				double lWithPotential = cost_[1][current][i] - potentialFunction(xy_[current], xy_[t], xy_[s]) + potentialFunction(xy_[neighbour], xy_[t], xy_[s]);
				double fakedist = lWithPotential + distWithPotential_[1][current];
				if (fakedist < distWithPotential_[1][neighbour])
				{
					Len realdist = distance_[1][current] + cost_[1][current][i];
					visit(q, 1, neighbour, fakedist);
					distance_[1][neighbour] = realdist;
					distWithPotential_[1][neighbour] = fakedist;
				}
			}
			// update result
			if (result > distance_[0][current] + distance_[1][current])
				result = distance_[0][current] + distance_[1][current];
			// check if currentback is visited
			if (visited_[current] == false)
				visited_[current] = true;
			else
				break;
		}
		if (result == infinite)
			return -1;
		return result;
	}
};
int main() {
	int n, m;
	cin >> n >> m;
	vector<pair<Len, Len>> xy(n);
	for (int i = 0; i < n; ++i) {
		int a, b;
		cin >> a >> b;
		xy[i] = make_pair(a, b);
	}
	Adj adj(2, vector<vector<int>>(n));
	Adj cost(2, vector<vector<int>>(n));
	for (int i = 0; i < m; ++i) {
		int u, v, c;
		cin >> u >> v >> c;
		adj[0][u - 1].push_back(v - 1);
		cost[0][u - 1].push_back(c);
		adj[1][v - 1].push_back(u - 1);
		cost[1][v - 1].push_back(c);
	}
	AStar astar(n, adj, cost, xy);
	int t;
	cin >> t;
	for (int i = 0; i < t; ++i) {
		int u, v;
		cin >> u >> v;
		cout << astar.query(u - 1, v - 1) << endl;
	}
}

Code em có 1 cái kỳ kỳ là, thay vì dùng hàm pow, em nhân 2 số lại với nhau thì speed up thêm 1 tí nữa, mà khi làm vậy thì sai test case 6/9. Còn dùng hàm pow thì TLE ở test case 8/9. Em không biết làm sao để speed up thêm nữa, em sắp give up rồi, mọi người em và góp ý giúp em với. Em xin cảm ơn nhiều!!

Với mỗi cụm

bạn có thể thay thế bằng hàm hypot(x, y), chức năng của hàm này là tính \sqrt{x^2+y^2}. Hạn chế bớt các hàm tính toán số thực để độ chính xác cao hơn và ít tốn thời gian hơn.

6 Likes

Không được rồi anh ơi, nó lên 74.03/50.00 s rồi

thử đừng xài cái này xem :V

3 Likes

:v sao mà không dùng được chứ.

mỗi lần enqueue nó cấp phát động lẻ tẻ gây mất thời gian :thinking: Nếu sau cùng đều push vào q[side] thì sao ko push thẳng luôn

3 Likes

Tại vì, khi lấy 1 cặp ra rồi kiểm tra xem có phải nó là cái đỉnh cần fix lại distance không chứ, nếu để vô lại thì lần sau lấy ra vẫn là nó mà (q chứa 2 min priority queue):v Em mới thay bằng vector, nó lên tới 99s luôn

q gì lạ vậy :V Sao lại phải pop ra đúng v thì mới chạy :V :V :V

à chắc đang lấn cấn vụ Dijkstra heap hả =] Để update dist của neighbor v thì ko cần pop queue lần lượt ra tới khi gặp nó đâu :V Đã có dist[v] rời ở ngoài rồi thì check newVDist với dist[v] này, nếu newVDist < dist[v] thì push cặp (newVDist, neighbor) vào q[side]. Như vậy trong q[side] lúc này có nhiều hơn 1 v, nhưng v có dist bé hơn sẽ ra trước, vẫn chạy đúng. Chỉ cần check các v sau đã visited chưa là được :V

4 Likes

Cái hàm này nó thay đổi mức độ ưu tiên của priority queue nè :v, xong rồi q là vector chứa 2 cái Queue. Tại vì cái hàm visit là hàm thay đổi distance mà, đúng vertex thì nó mới thay đổi, còn nếu ko có trong priority queue, tức là nó chưa được thêm vô, nó rơi vào trường hợp phía trên rồi á :v

ví dụ

  • q chứa (2,1), (4,2)
  • pop ra (2,1), check visited[1], thấy false, tiếp tục: q còn (4,2). 1 có nhánh 1-2 có cost là 1, vậy new dist của 2 là 2+1 = 3. Check 3 với - dist[2] là 4 thì thấy 3 < 4, vậy push (3,2) vào q và update dist[2] = 3. q lúc này chứa (3,2), (4,2). (nếu new dist > 4 thì đừng push giá trị mới vào)
  • pop ra (3,2), check visited[2], thấy false, tiếp tục xử lý. Sau khi xử lý q chứa (4,2), …
  • pop ra (4,2), check visited[2], thấy true, continue (bỏ qua)

ko cần phải pop q tới khi gặp (…, 2) mới xử lý 2 đâu :V Chỉ cần thêm 1 check visited[current] trước khi xét neighbor của current là được.

về bài dijkstra sửa lại cho đúng cái đi, xem thời gian có lẹ hơn 10 giây ko rồi qua bài A* sửa tiếp =]]

4 Likes

Em hiểu idea của anh rồi nè, để em làm thử nha rồi feedback sau :v cảm ơn anh nhiều lắm.

Ok anh :v

mã “giả” của Dijkstra =]

std::vector<int> dist(n, kInfinite);
std::vector<bool> visited(n, false);

// Tạo min pq với comparator từ mảng `dist` bên ngoài
auto weightedVertexComp = [&](int u, int v){
    return dist[u] > dist[v]; // > to make min pq
};
std::priority_queue<int, std::vector<int>, decltype(weightedVertexComp)> q(weightedVertexComp);

dist[0] = 0; // vì so sánh dựa vào `dist` bên ngoài nên PHẢI đổi giá trị của dist[u] trước khi push u vào q
q.push(0);

while (!q.empty()) {
    int u = q.front();
    q.pop();
    
    // Check for duplicates
    if (visited[u]) continue; 
    
    for (auto& v : neighbors(u)) {
        if (visited[v]) continue;
        int newDistV = dist[v] == kInfinite ? edgeWeight(u, v) : dist[v] + edgeWeight(u, v);
        if (newDistV < dist[v]) {
            dist[v] = newDistV; // update dist[v] trước khi push v vào q
            q.push(v);
        }
    }
}

cái q thật ra ko cần chứa cả dist của v đâu :V Có điều code xấu hơn nhiều :V :V

std::priority_queue<int, std::vector<int>, decltype(weightedVertexComp)> q(weightedVertexComp);
4 Likes

Nếu như check visited trước khi chạy dijsktra thì nó sẽ bị lỗi ở chỗ gặp mid point ấy anh, cái Forward Graph chạy trước, gặp tr thì sẽ ko có vấn đề gì, nhưng rồi khi Back Ward gặp lại điểm đó, nó continue điểm đó luôn mà ko update được cái result lên, vậy thì nó sẽ sai :v Cái bug này khi làm bài Friend request em có bị rồi. Mà nếu như ko chạy được Dijsktra thì nó còn xảy ra trường hợp này nữa nè:


Tới đây nó không biết đi đường nào, rồi nó đi cái neighbour đầu tiên có thể dẫn đến sai kết quả.

xài 2 mảng visited khác nhau :joy: forward check visited của forward, backward check visited của backward :V :V :V

4 Likes

Theo kinh nghiệm chơi CP của mình thì:

  • Dùng pair constructor thay cho make_pair
  • Dùng emplace_back thay cho push_back
  • Chỗ nào không cần dùng vector thì đổi sang mảng
  • Hạn chế pass by value mà đổi sang dùng biến ở scope lớn hơn hoặc pass by reference

Chắc là sẽ ăn bớt được tí time :grin:

4 Likes

Em cảm ơn sự góp ý của 2 anh

Có người bảo relax distance trong hàm queries thay vì trong hàm visit có thể giảm được 1/3 thời gian, em cũng thử như thế nhưng mà nó lại ko ăn thua.

Bidirectional Dijkstra, C++17 :V

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <utility>
#include <limits>

using Vertex = unsigned int;
using AdjacentList = std::vector<std::vector<Vertex>>;
using Weight = long long;
using AdjacentWeightList = std::vector<std::vector<Weight>>;
const Weight kInfinite = std::numeric_limits<Weight>::max();

namespace details
{
template <class MinPQ, class Condition, class BestDistUpdateFunction>
auto process(const AdjacentList& adj, const AdjacentWeightList& adjW, MinPQ& q, std::vector<Weight>& dist,
             std::vector<bool>& visited, Condition breakPrecondition, BestDistUpdateFunction updateBestDist) -> bool {
    //
    if (q.empty() || breakPrecondition(q.top())) {
        return false;
    }
    // Get next vertex in q
    auto u = q.top();
    q.pop();
    if (visited[u]) { // Fix priority_queue multiple entries
        return process(adj, adjW, q, dist, visited, breakPrecondition, updateBestDist);
    }
    // Relax u's adjacent vertices' dists
    for (size_t vi = 0; vi < adj[u].size(); ++vi) {
        if (Vertex v = adj[u][vi]; !visited[v]) {
            if (Weight newVWeight = dist[u] + adjW[u][vi]; newVWeight < dist[v]) {
                dist[v] = newVWeight; // dist[v] must be set before pushing v to q
                q.emplace(v);
            }
            updateBestDist(u, v); // (*): updateBestDist must be called after updating dist[v]
        }
    }
    // Finalized
    visited[u] = true;
    return true;
}

template <class Condition>
auto distDijkstra(const AdjacentList& adj, const AdjacentWeightList& adjW, Vertex s, Condition breakPrecondition)
    -> std::vector<Weight> {
    auto n = adj.size();
    std::vector<Weight> dist(n, kInfinite);
    std::vector<bool> visited(n, false);
    auto vertexWeightComp = [&](Vertex u, Vertex v) { return dist[u] > dist[v]; };
    std::priority_queue<Vertex, std::vector<Vertex>, decltype(vertexWeightComp)> q(vertexWeightComp);
    dist[s] = 0; // dist[s] must be set before pushing s to q
    q.emplace(s);
    while (
        process(adj, adjW, q, dist, visited, breakPrecondition, [](auto /*unused*/, auto /*unused*/) { /*no-op*/ })) {
    }
    return dist;
}
} // namespace details

auto distDijkstra(const AdjacentList& adj, const AdjacentWeightList& adjW, Vertex s) -> std::vector<Weight> {
    return details::distDijkstra(adj, adjW, s, [](Vertex u) { return false; });
}

auto distDijkstra(const AdjacentList& adj, const AdjacentWeightList& adjW, Vertex s, Vertex t) -> Weight {
    auto dist = details::distDijkstra(adj, adjW, s, [&](Vertex next) { return next == t; });
    return dist[t];
}

auto distBidirectionalDijkstra(const AdjacentList& adj, const AdjacentWeightList& adjW,   //
                               const AdjacentList& adjR, const AdjacentWeightList& adjWR, //
                               Vertex s, Vertex t) -> Weight {
    if (s == t) {
        return 0;
    }
    //
    auto n = adj.size();
    // Forward Dijkstra
    std::vector<Weight> dist(n, kInfinite);
    std::vector<bool> visited(n, false);
    auto vertexWeightComp = [&](Vertex u, Vertex v) { return dist[u] > dist[v]; };
    std::priority_queue<Vertex, std::vector<Vertex>, decltype(vertexWeightComp)> q(vertexWeightComp);
    dist[s] = 0; // dist[s] must be set before pushing s to q
    q.emplace(s);
    // Backward Dijkstra
    std::vector<Weight> distR(n, kInfinite);
    std::vector<bool> visitedR(n, false);
    auto vertexWeightCompR = [&](Vertex u, Vertex v) { return distR[u] > distR[v]; };
    std::priority_queue<Vertex, std::vector<Vertex>, decltype(vertexWeightCompR)> qR(vertexWeightCompR);
    distR[t] = 0; // dist[t] must be set before pushing t to qR
    qR.emplace(t);

    Weight bestDistSoFar = kInfinite;
    while (
        // Forward search
        details::process(
            adj, adjW, q, dist, visited,
            [&](auto /*unused*/) { return qR.empty() ? false : dist[q.top()] + distR[qR.top()] >= bestDistSoFar; },
            [&](Vertex u, Vertex v) { //
                if (visitedR[v]) {
                    bestDistSoFar = std::min(bestDistSoFar, dist[v] + distR[v]); // dist[v] must be updated before (*)
                }
            }) //
        &&
        // Backward search
        details::process(
            adjR, adjWR, qR, distR, visitedR,
            [&](auto /*unused*/) { return q.empty() ? false : dist[q.top()] + distR[qR.top()] >= bestDistSoFar; },
            [&](Vertex u, Vertex v) { //
                if (visited[v]) {
                    bestDistSoFar = std::min(bestDistSoFar, distR[v] + dist[v]); // distR[v] must be updated before (*)
                }
            }) //
    ) {
    }
    return q.empty() || qR.empty() ? kInfinite : bestDistSoFar;
}

auto readGraph(std::istream& is, bool undirected) {
    int m = 0;
    int n = 0;
    is >> n >> m;
    AdjacentList adj(n);
    AdjacentWeightList adjW(n);
    while (m-- > 0) {
        Vertex u = 0;
        Vertex v = 0;
        Weight w = 0;
        is >> u >> v >> w;
        --u;
        --v;
        adj[u].push_back(v);
        adjW[u].push_back(w);
        if (undirected) {
            adj[v].push_back(u);
            adjW[v].push_back(w);
        }
    }
    return std::make_pair(adj, adjW);
}

auto buildReverseGraph(const AdjacentList& adj, const AdjacentWeightList& adjW) {
    AdjacentList adjR(adj.size());
    AdjacentWeightList adjWR(adj.size());
    for (Vertex u = 0; u < adj.size(); ++u) {
        for (size_t vi = 0; vi < adj[u].size(); ++vi) {
            Vertex v = adj[u][vi];
            Weight w = adjW[u][vi];
            adjR[v].push_back(u);
            adjWR[v].push_back(w);
        }
    }
    return std::make_pair(adjR, adjWR);
}

auto main() -> int {
    auto [adj, adjW] = readGraph(std::cin, false);
    auto [adjR, adjWR] = buildReverseGraph(adj, adjW);
    int t = 0;
    std::cin >> t;
    while (t-- > 0) {
        Vertex s = 0;
        Vertex t = 0;
        std::cin >> s >> t;
        std::cout << distBidirectionalDijkstra(adj, adjW, adjR, adjWR, s - 1, t - 1) << "\n";
    }
}

em thử nộp coi chạy lẹ hơn ko =]

mà có cho phép xài C++17 ko :V :V

4 Likes


Anh viết thiếu cái adj_ rồi nè :v. Em chưa từng nộp bằng c++17 nên em cũng ko biết nữa.

vô Project -> <tên project> Properties chỉnh compiler lại thành C++17 :V

nộp thử, hên xui =]] ko đc thì sửa tí là được =]

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?