Cod sursa(job #2822242)

Utilizator mentolnothingmoreNegrut Maria Daniela mentolnothingmore Data 23 decembrie 2021 18:49:00
Problema Parcurgere DFS - componente conexe Scor 100
Compilator cpp-64 Status done
Runda Arhiva educationala Marime 48.98 kb
#include <bits/stdc++.h>

using namespace std;

/// For disjoint sets
class Forest
{
private:
    int parent, size;
public:
    static vector<Forest> MakeForest(const int& n)
    {
        vector<Forest> F(n + 1);
        for (int i = 1; i <= n; ++i)
        {
            F[i].parent = i;
            F[i].size = 1;
        }

        return F;
    }

    static int FindParent(int x, vector<Forest>& F)
    {
        while (F[x].parent != x)
        {
            F[x].parent = F[ F[x].parent].parent;
            F[x].size = F[F[x].parent].size;
            x = F[x].parent;
        }
        return x;
    }

    static void UnionForest(int x, int y, vector<Forest>& F)
    {
        x = FindParent(x, F);
        y = FindParent(y, F);

        if (x == y) return;

        if (F[x].size  > F[y].size)
        {
            F[y].parent = x;
            F[x].size += F[y].size;
        }
        else
        {
            F[x].parent = y;
            F[x].size += F[x].size;
        }
    }

    static bool CheckForest(int x, int y, vector<Forest>& F)
    {
        if (FindParent(x, F) == FindParent(y, F))
            return 1;
        return 0;
    }
};

class Edge
{
    //class that helps with the definition of edges, with costs and or capacity
public:
    int from, to;
    int weight;
    int capacity;
    Edge(const int& f=0, const int& w=0, const int& wg = 0, const int& cap = 0)
    {
        from = f;
        to = w;
        weight = wg;
        capacity = cap;
    }
     bool operator()(const Edge &left, const Edge& right) // for priority queue compare
    {
        return left.weight > right.weight;
    }
};

/// Function used to sort a vector of pairs descending by the second component
bool SortDescPair(const pair<int,int>& i, const pair<int,int>& j)
{
    return i.second > j.second;
}

class Graph
{
 private:
    int _nr_vertex; // Number of vertexes
    int _nr_edge; // Number of edges
    bool _oriented; // Mark if the graph is oriented
    bool _weighted; // Mark if the graph is weighted
    bool _flow; // Mark if it is a flow network
    vector<vector<Edge>> _adjacency;

    vector<int> _popEdges(stack<Edge>& , const Edge&);
    void _popVertex(const int&, stack<int>&, int&, vector<vector<int>>&, vector<bool>&);
    int  _bfs(const int&, int&);
    vector<int> _dist(const int&);
    void _dfs(const int&, const int&, vector<int>&, stack<int>&);
    void _leveledDfs(const int&, vector<int>& , vector<int>&, vector<int>& , stack<Edge>&, vector<vector<int>>&, vector<vector<int>>&);
    void _Tarjan(const int&, int&, vector<int>&, vector<int>&, stack<int>&, vector<bool>& , int&, vector<vector<int>>&);
    bool _HavelHakimi(const int&, const int&, vector<pair<int,int>>& );
    void _addEdge(const Edge&);
    void _resize(const int&, const int&, const bool&, const bool&, const bool&);
    int _flowBfs(const vector<vector<int>>&, const int&, const int&, vector<int>&, vector<vector<int>>&, vector<vector<int>>&);
    void _initializeAdMat(vector<vector<int>>&);

 public:
    Graph(const bool& oriented = 0, const bool& weighted = 0, const bool &flow = 0);

    void AddEdge(const int& from, const int& to, const int& cost = 0, const int& capacity = 0);
    void clear();
    void resize(const int& new_nr_nodes);
    int nrVertex();
    int nrEdges();
    void MakeOriented();
    void MakeNonOriented();
    void MakeWeighted();
    void MakeNonWeighted();
    void MakeFlowNetwork();
    void MakeNotFlowNetwork();

    friend istream& operator>>(istream& in, Graph& g);
    friend ostream& operator<<(ostream& out, const Graph& g);

    vector<int> DF(const int&);
    vector<int> BF(const int&);
    int NrConnectedComponents();
    vector<int> DistanceFrom(const int&);
    pair<int,vector<Edge>> Prim(const int&);
    vector<vector<int>> RoyFloyd(vector<vector<int>>& cost_matrix);
    vector<int> TopologicalSort();
    pair<int,vector<vector<int>>> BiconnectedComponents();
    vector<vector<int>> CriticalConnections();
    pair<int, vector<vector<int>>> StronglyConnectedComponents();
    void HavelHakimi(istream&);
    static bool VerifyHavelHakimi(const int&, const int&, vector<pair<int,int>>&);
    tuple<int, int, vector<Edge>> MinimumSpanningTree();
    vector<int> Dijkstra(const int&);
    vector<int> BellmanFord(const int&);
    int  MaxFlow(const int&, const int &);
    int Diameter();
    vector<int> EulerianCycle();
};

istream& operator>>(istream& in, Graph& g)
{
    in >> g._nr_vertex >> g._nr_edge;

    g._adjacency.resize(g._nr_vertex + 1);
    int x, y, c = 0, capacity = 0;
    for(int i = 0; i < g._nr_edge; ++i)
    {
        in >> x >> y;
        if (g._weighted == 1)
        {
            in >> c;
        }
        if (g._flow == 1)
        {
            in >> capacity;
        }
        g._addEdge({x, y, c, capacity});
    }

    return in;
}

ostream& operator<<(ostream& out, const Graph& g)
{
    if (g._oriented)
        out << "Oriented";
    else out << "Not oriented";
    if (g._weighted)
        out << " weighted";
    out << " graph";
    if (g._flow)
        out <<" with flow";
    out << "\nNumber of vertexes: " << g._nr_vertex
        << "\nNumber of edges: " << g._nr_edge;
    out << "\nEdges are: \n";

    for (int i = 1; i <= g._nr_vertex; ++i)
    {
        for(auto &edge: g._adjacency[i])
        {
             out  << i << " to " << edge.to;
             if (g._weighted)
                out << " with cost " << edge.weight;
             if (g._flow)
                out << " with capacity " << edge.capacity;
            out << "\n";
        }
    }

    return out;
}

Graph::Graph(const bool& oriented, const bool& weighted, const bool& flow)
{
   _nr_vertex = 0;
   _nr_edge = 0;
   _oriented = oriented;
   _weighted = weighted;
   _flow = flow;
}

void Graph::_addEdge(const Edge& e)
{
    /// Add an edge to the graph
    _adjacency[e.from].push_back(Edge(e.from, e.to, e.weight, e.capacity));
    if (!_oriented) _adjacency[e.to].push_back(Edge(e.to, e.from, e.weight, e.capacity));
}

vector<int> Graph::_dist(const int& startV)
{
    /// Solves BFS  returning the distances vector
    //startV = the start vertex of the bfs
    //return: distances = vector of _nr_vertex + 1 length with the distances from the start vertex to the i-th one

    vector<int> distances( _nr_vertex + 1, -1);
    distances[startV] = 0;
    queue<int> order_list;
    order_list.push(startV); // starting BFS from the given vertex
    int current;

    while (!order_list.empty())
    {
        current = order_list.front();
        for (auto &neighbour: _adjacency[current])
        {
            if (distances[neighbour.to] == -1)
            {
                distances[neighbour.to] = distances[current] + 1;
                order_list.push(neighbour.to);
            }
        }
        order_list.pop();
    }
    return distances;
}

int Graph::_bfs(const int& s, int& last)
{
    /// Solves BFS  returning the longest distance
    //s = start node
    // last = last accessed node

    vector<int> distances(_nr_vertex + 1, 0);
    vector<int>visited(_nr_vertex + 1, 0);
    queue<int> order_list;
    order_list.push(s);
    int current, diameter = 0;

    distances[s] = 1;
    visited[s] = 1;
    while (!order_list.empty())
    {
        current = order_list.front();
        order_list.pop();
        for (auto &neighbour: _adjacency[current])
        {
            if (visited[neighbour.to] == 0)
            {
                visited[neighbour.to] = 1;
                distances[neighbour.to] = distances[current] + 1;
                diameter = max(diameter, distances[neighbour.to]); // longest distance starting with s
                order_list.push(neighbour.to);
            }
        }
    }
    last = current;
    return diameter;
}

void Graph::_dfs(const int& start, const int& marker, vector<int>& mark, stack<int>& exit_time)
{
    /// Solves DFS recursively and marks all nodes component
    //start = start vertex
    // marker = value to use when finding a visited vertex
    // mark = vector of _nr_vertex + 1 elements; retains the marker of each vertex
    // exit_time = stack in the order of exiting vertex's DFS

    mark[start] = marker;
    for (auto &neighbour: _adjacency[start])
    {
        if (mark[neighbour.to] == -1)
            _dfs(neighbour.to, marker, mark, exit_time);
    }

    exit_time.push(start);
}

vector<int> Graph::_popEdges(stack<Edge>& st, const Edge& last_edg) // used in leveled_dfs
{
    /// Given an stack of edges pop the elements of the stack till we reach the one given
    // returning the vector of the removed edges
    vector<int> sol;
    int x, y;
    do
    {
        x = st.top().from;
        y = st.top().to;
        st.pop();
        sol.push_back(x);
        sol.push_back(y);
    }while (x != last_edg.from && y !=last_edg.to);

    return sol;
}

void Graph::_popVertex(const int& start, stack<int>& st, int& sol_nr, vector<vector<int>>& sol, vector<bool> &in) //used in leveled_dfs
{
    /// Given a stack of vertex's  pop the elements of the stack till we reach the one given
    // sol_nr = number of strong connected components
    //sol[i] = vertex's of the strong connected component
    //in[i] = the vertex i was visited in the current component

    sol_nr++; // found a new component
    int aux;
    vector<int> aux2;
    do
    {
        aux = st.top();
        in[aux] = 0;
        st.pop();
        aux2.push_back(aux);
    }while (aux != start);
    sol.push_back(aux2); // add the new list to the solution
}

void Graph::_leveledDfs(const int& start, vector<int>& parent, vector<int>& level, vector<int>&  return_level, stack<Edge>& expl_edges, vector<vector<int>>& biconex_comps, vector<vector<int>>& critical_edges)
{
    /// Given a start vertex do a recursive modified DFS, with the purpose of finding the critical vertex's/edges (similar to Tarjan's algorithm)
    // start = current vertex in the dfs
    //level [i] = depth of the vertex in the dfs tree
    //parent[i] = the parent of the vertex i in the dfs tree
    //return_level[i] = the level that the vertex i can return to using return edges
    //expl_edges = stack of edges in the order of discovery - used to discover the biconex components
    //biconex_comps = vector of  the biconex components and their vertex's
    //critical_edges[i] = vector of 2 elements (leetcode restriction) that signify a critical edge

    int nr_children = 0;
    for (auto &child: _adjacency[start])
    {
        nr_children ++; // mark as child
        if (parent[child.to] == -1)
        {
            expl_edges.push(Edge(start, child.to));
            parent[child.to] = start;
            level[child.to] = level[start] + 1;
            return_level[child.to] = level[child.to]; // now the lowest level  reached  from the child is his exact level

            _leveledDfs(child.to, parent, level, return_level, expl_edges, biconex_comps, critical_edges);

            return_level[start] = min(return_level[start], return_level[child.to]); // passed the rest of the DF tree of the child
            // can modify the lowest level reached in case there was a vertex with a return edge

            if (return_level[child.to] > level[start]) // the child cant return higher then his parent
            {
                critical_edges.push_back(vector<int>{start,child.to, 0}); // so this edge is critical in the graph
            }

            if (parent[start] == 0 && nr_children >= 2)// if the root of DF tree has 2 or more  children, means it is a critical vertex
            {
                vector<int> new_comp = _popEdges(expl_edges, {start,child.to});  // get the biconex component of start
                biconex_comps.push_back(new_comp);
            }

            if (parent[start] != 0 && return_level[child.to] >= level[start]) // the child can return to a lower level then his parent
            {
                // so we reached the end of a biconex component
                vector<int> new_comp = _popEdges(expl_edges, {start,child.to}); // get the biconex component of start
                biconex_comps.push_back(new_comp);
            }
        }

        else if (child.to != parent[start]) return_level[start] = min(return_level[start], level[child.to]); // update the lowest level reachable with return edges
    }
}

void Graph::_Tarjan(const int& start, int& time, vector<int>& in_time, vector<int>& return_time, stack<int>& connection, vector<bool>& in_connection, int& nr_ctc, vector<vector<int>>& strong_connected)
{
    /// Given a vertex determine his strong connected component recursively (DF based)
    //start = current vertex
    //time = the nr. of  iterations, meaning the time of discovery of a vertex in DF tree
    //in_time[i] = discovery time of i
    //return_time[i] = the lowest discovery time in the i DF sub-tree  + return edges
    //in_connection[i] = true if i is in the current SCC
    //strong_connected & nr_ctc = nr of strong connected components and the respective vertex's

    time++; // mark a new iteration
    in_time[start] = time;// mark the discovery time of vertex
    return_time[start] = time; // no return edge known
    in_connection[start] = 1;
    connection.push(start);

    for (auto &child: _adjacency[start])
    {
        if (in_time[child.to] == -1)
        {
            _Tarjan(child.to, time,  in_time, return_time, connection, in_connection, nr_ctc, strong_connected);
            return_time[start] = min(return_time[start], return_time[child.to]); // update in case a return edge was found in the child's  df sub-tree
        }
        else if(in_connection[child.to])
        {
            // the child was discovered and in the same SCC as parent => return edge exists
            // update in case child was discovered before the last child that updated
            return_time[start] = min(return_time[start], in_time[child.to]);
       }
    }

    if (return_time[start] == in_time[start]) // a vertex that doesn't have a return edge => end of SCC
    {
        _popVertex(start, connection, nr_ctc, strong_connected, in_connection);
    }
}

bool Graph::_HavelHakimi(const int& n, const int& nr_d, vector<pair<int,int>>& degrees)
{
    /// Given a vector of pairs (Vertex, degree) determine if it is a valid graph
    // and actually makes the graph not just validates
    int  m = nr_d/2; // number of edges is sum of degrees/2
    resize(n);

    vector<vector<bool>> matrix_adj; // access to finding if a edge exists in O(1). but memory + O(n^2)
    matrix_adj.resize(n + 1, vector<bool>(n + 1, 0));

    pair<int,int> max_dg;
    while (m != 0) //the max nr. of iterations is the number of edges in the supposed graph
    {
        sort(degrees.begin(), degrees.end(), SortDescPair); // sort the degrees descending
        if (degrees.size() == 0 || degrees[0].second == 0)
            break; // no vertex left with degree > 0

        max_dg.first = degrees[0].first;
        max_dg.second = degrees[0].second;
        degrees.erase(degrees.begin());

        for (unsigned int k = 0; k < degrees.size() && max_dg.second > 0 ; ++k)
        {
            if (matrix_adj[max_dg.first][degrees[k].first] != 0)
            {
                matrix_adj[max_dg.first][degrees[k].first] = 1; // mark added edge
                matrix_adj[degrees[k].first][max_dg.first] = 1;

                degrees[k].second--; // mark added edge in degrees left
                max_dg.second--;

                if (degrees[k].second < 0) // can't add edges
                    return 0;

                m--;
                AddEdge(max_dg.first, degrees[k].first, 0, 0); // add the edge to the graph
            }
        }

        if (max_dg.second != 0) // didn't add the required edges
            return 0;
    }

    if (m != 0) return 0; // Couldn't add the number of desired edges so not a valid graph

    return 1;
}

int Graph::_flowBfs(const vector<vector<int>>& residual, const int& source, const int& dest, vector<int>& parent, vector<vector<int>>& capacity, vector<vector<int>>& flow)
{
    /// BFS (from start to destination) variation for flux network returning the parent of the destination node given
    queue<int> order_list;
    order_list.push(source);
    parent.assign(_nr_vertex + 1, -1);
    parent[source] = 0;
    int current;

    while (!order_list.empty() && parent[dest] == -1) // we didnt reach the dest
    {
        current = order_list.front();
        order_list.pop();

        // go through the nodes adjacent of the current one
        // and go trough the residual because we update both ways when finding maximum flow
        // maximize the income flux (flow[parent][child])
        // and minimize not used flux (flow[child][parent])
        for (int next: residual[current])
        {
            if (parent[next] == -1 && capacity[current][next] > flow[current][next]) // if it's not visited and can change the flow of the path
            {
                parent[next] = current;
                order_list.push(next);
            }
        }
    }
    return parent[dest]; // return the parent of the dest node (-1 if not found in path)
}

void Graph::_initializeAdMat(vector<vector<int>>& matrix)
{
    // Make copy of adjacency matrix
    matrix.resize(_nr_vertex + 1);

    for (int i = 1; i <= _nr_vertex; ++i)
    {
        for (auto &edge: _adjacency[i])
            matrix[i].push_back(edge.to);
    }
}

int Graph::nrVertex()
{
    return _nr_vertex;
}

int Graph::nrEdges()
{
    return _nr_edge;
}

void Graph::MakeFlowNetwork()
{
    /// Clears adjacency list and makes flow network
    _adjacency.clear();
    _flow = 1;
}

void Graph::MakeNotFlowNetwork()
{
    /// Clears adjacency list and removes flow network marking
    _adjacency.clear();
    _flow = 0;
}

void Graph::MakeOriented()
{
    /// Clears adjacency list and makes oriented graph
    _adjacency.clear();
    _oriented = 1;
}

void Graph::MakeNonOriented()
{
    /// Clears adjacency list and makes non-oriented graph
    _adjacency.clear();
    _oriented = 0;
}

void Graph::MakeWeighted()
{
    /// Clears adjacency list and makes weighted graph
    _adjacency.clear();
    _weighted = 1;
}

void Graph::MakeNonWeighted()
{
    /// Clears adjacency list and makes non-weighted graph
    _adjacency.clear();
    _weighted = 0;
}

void Graph::AddEdge(const int& from, const int& to, const int& cost, const int& capacity)
{
    int realCost = 0, realCap = 0;
    if (_weighted) // Dont allow to add cost if not weighted graph
        realCost = cost;
    if (_flow) // Dont allow to add capacity if not flow network
        realCap = capacity;

    _addEdge(Edge(from, to, realCost, realCap));
    _nr_edge++;
}

void Graph::clear()
{
    _nr_vertex = 0;
    _nr_edge = 0;
    _oriented = 0;
    _weighted = 0;
    _flow = 0;
    _adjacency.clear();
}

void Graph::resize(const int& nr_vertex)
{
     _nr_edge = 0;
    _nr_vertex = nr_vertex;
    _adjacency.clear();
    _adjacency.resize(_nr_vertex + 1);
}

vector<int> Graph::BF(const int& start)
{
    /// Breadth first graph transversal (visiting nodes once)
    // Returns vector with nodes in order
    if(start <1 || start > _nr_vertex)
        return vector<int>();

    vector<int> sol;
    vector<bool> visited(_nr_vertex+ 1, 0);
    queue<int> order_list;
    order_list.push(start);
    sol.push_back(start);
    visited[start] = 1;
    int current;

    while (!order_list.empty())
    {
        current = order_list.front();
        for (auto &neighbour: _adjacency[current])
        {
            if (visited[neighbour.to] == 0)
            {
                sol.push_back(neighbour.to);
                order_list.push(neighbour.to);
                visited[neighbour.to] = 1;
            }
        }
        order_list.pop();
    }

    return sol;
}

vector<int> Graph::DF(const int& start)
{
    /// Depth first graph transversal (visiting nodes once)
    // On the same level the nodes are from level end to level start
    if(start <1 || start > _nr_vertex)
        return vector<int>();

    vector<int> sol;
    stack<int> order;
    vector<int> visited(_nr_vertex + 1, -1);

    _dfs(start, 1, visited, order);

    while (!order.empty())
    {
        sol.push_back(order.top());
        order.pop();
    }

    return sol;
}

pair<int, vector<Edge>> Graph::Prim(const int& start)
{
    /// Prim's Algorithm
    // returning the cost of the MST and the vector of edges that compose it
    if (!_weighted || start < 1 || start > _nr_vertex)
        return make_pair(0, vector<Edge>());

    priority_queue<Edge, vector<Edge>, Edge> heap; // heap of min edge cost
    vector<int> cost(_nr_vertex + 1, INT_MAX); // min cost of every node
    vector<int> parent(_nr_vertex + 1, -1);
    vector<bool> in_APM(_nr_vertex + 1, 0); // keep track of components
    vector<Edge> edges; // for returning the edges in the MST

    int node, total_cost = 0;
    cost[start] = 0; // the cost of staying in place is 0

    heap.push({-1, start, cost[start], 0});

    while (!heap.empty())
    {
        node = heap.top().to;
        heap.pop();
        if (!in_APM[node])
        {
            in_APM[node] = 1;
            total_cost += cost[node]; // it is the min cost  in the heap

            for (auto &neighbour: _adjacency[node]) // go through neighbors
            {
                if (cost[neighbour.to] > neighbour.weight && !in_APM[neighbour.to])
                { // actualize the cost of the current nodes neighbors if their not already in the tree
                    heap.push(neighbour); // add in heap
                    parent[neighbour.to] = node;
                    cost[neighbour.to] = neighbour.weight;
                }
            }

            if (parent[node] != -1) // make sure we dont punt the -1 - start edge that is used to get the cost of  s
                edges.push_back({parent[node], node, cost[node], 0});
        }
    }

    return make_pair(total_cost, edges); // return cost and vector of edges
}

vector<int> Graph::Dijkstra(const int& start)
{
    /// Dijkstra algorithm for finding the minimum cost from start to all the nodes
    // assume there are no negative cycles.
     if (!_weighted || start < 1 || start > _nr_vertex)
        return vector<int>(1,-1);

    priority_queue<Edge, vector<Edge>, Edge> heap;// make heap of min cost
    vector<int> dist(_nr_vertex + 1, INT_MAX);
    vector<bool> in(_nr_vertex + 1, 0);
    int node;
    dist[start] = 0; // cost of staying in place is 0
    in[start] = 1;

    heap.push({0, start, dist[start], 0});
    while (!heap.empty())
    {
        node = heap.top().to;
        heap.pop();

        for (auto &neighbour: _adjacency[node]) //go trough neighbors
        {
             // actualize the cost of getting from start to node
            if (dist[neighbour.to] > dist[node] + neighbour.weight && !in[neighbour.to])
            {
                in[neighbour.to] = 1; // mark visited
                dist[neighbour.to] = dist[node] + neighbour.weight;
                heap.push({neighbour.from, neighbour.to, dist[neighbour.to], 0});
            }
        }
    }

    return dist;
}

int Graph::MaxFlow(const int& source, const int& dest)
{
    /// Edmonds Karp algorithm for computing the maximum flow
    if (!_flow || (source < 1 || source > _nr_vertex) || (dest < 1 || dest > _nr_vertex))
        return -1;

    vector<int> parent;
    vector<vector<int>> residual(_nr_vertex + 1);
    vector<vector<int>> capacity_matrix(_nr_vertex + 1, vector<int>(_nr_vertex + 1, 0)); // for easy access of edge capacity (+ O(n^2) memory and O(m) time for initialization)
    vector<vector<int>> flow_matrix(_nr_vertex + 1, vector<int>(_nr_vertex + 1, 0)); // for easy access of  edge flow

    for (int node = 1; node <= _nr_vertex; ++ node)
    {
        for (auto &edge:_adjacency[node])
        {
            capacity_matrix[node][edge.to] = edge.capacity; // initialize capacity
            residual[node].push_back(edge.to); // mark the neigbours despite the direction (because its residual graph)
            residual[edge.to].push_back(node);
        }
    }

    int max_flow = 0, n_flow;

    while (_flowBfs(residual, source, dest, parent, capacity_matrix, flow_matrix) != -1) // there are optimizations to be made
    {
        for (int k: residual[dest]) // go trough all the nodes adjacent to the destination and optimize those
        {
                if (parent[k] != -1 && capacity_matrix[k][dest] > flow_matrix[k][dest])// only if the edge isnt at max capacity and is found in bfs tree
                {
                       parent[dest] = k; // assume the parent is k
                       n_flow = INT_MAX; // assume the new flow is infinite

                       int  n = dest; // going upwards through the graph's bfs tree
                       while (n != source)
                       {
                           // determine the flow that can be optimized
                           // which is the minimum unused flow of the path
                            n_flow = min(n_flow, capacity_matrix[parent[n]][n] - flow_matrix[parent[n]][n] ); // make the flow of the path
                            n = parent[n];
                       }

                       if (n_flow > 0) // if it actually makes sense to update
                       {
                           n = dest;// going upwards through the graph's bfs tree
                           while (n != source)
                           {
                                flow_matrix[parent[n]][n] += n_flow; //update what the parent sends to child
                                flow_matrix[n][parent[n]] -= n_flow; // update what the child gets from parent
                                n = parent[n];
                           }

                           max_flow += n_flow; // update the maximum flow
                       }
              }
        }
    }

    return max_flow;
}

vector<vector<int>> Graph::RoyFloyd(vector<vector<int>>& dist)
{
    /// Roy-Floyd algorithm for making the minimum cost matrix
    if (!_weighted)
        return (vector<vector<int>>());

    for (int k = 1; k <= _nr_vertex; ++k)
    {
        for (int i = 1; i <= _nr_vertex; ++i)
        {
            for (int j = 1; j <= _nr_vertex; ++j)
            {
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX && dist[i][j] > dist[i][k] + dist[k][j])
                {
                    // update cost from i to j by calculating the costs from an intermediate node
                    dist[i][j] = dist[i][k] + dist[k][j];
                }
            }
        }
    }

    return dist;
}

int Graph::Diameter()
{
    /// Longest path in a tree
    if (_nr_edge != _nr_vertex - 1)
        return -1;
    // last = last node that gets max path length - so it's the furthest leaf from the root
    int last, aux;
    _bfs(1, last);
    // from last we go back to the root and then descend into the other furthest leaf from root
    return  _bfs(last, aux);
}

vector<int> Graph::DistanceFrom(const int& start)
{
    /// Return distance from start to the other nodes
    if (start < 1 || start > _nr_vertex)
        return vector<int>(1, -1);
    vector<int> result = _dist(start);
    return vector<int> (result.begin() + 1, result.end());
}

int Graph::NrConnectedComponents()
{
    /// Number of connected components of graph
    int result = 0;
    vector<int> components(_nr_vertex + 1, -1);
    stack<int> aux; // not needed in the solving this, but _dfs does multiple things

    for (int i = 1; i <= _nr_vertex; ++i)
    {
        if (components[i] == -1) // discover new component
        {
            result++;
            _dfs(i, result, components, aux);
        }
    }
    return result;
}

vector<int> Graph::TopologicalSort()
{
    /// Sort DAG topologically
    if (!_oriented)
        return vector<int>();

    vector<int> visited(_nr_vertex + 1, -1);
    vector<int> sol;
    stack<int> aux;

    for (int i = 1; i <= _nr_vertex; ++i)
    {
        if (visited[i] == -1) // not visited so we go through it
        {
            _dfs(i, 1, visited, aux); // discover all nodes
        }
    }

    while (!aux.empty()) // pop the nodes in the order of exiting DFS
    {
        sol.push_back(aux.top());
        aux.pop();
    }

    return sol; // vector of vertex's in sorted order
}

pair<int,vector<vector<int>>> Graph::BiconnectedComponents()
{
    /// Biconnected components of graph (number and each component)
    vector<vector<int>> sol;
    vector<int> parent(_nr_vertex + 1, -1);
    vector<int> level(_nr_vertex + 1, -1);
    vector<int> rtr_lvl(_nr_vertex + 1, -1);
    stack<Edge> st;
    vector<vector<int>> crt_edg;

    // using a modified DFS
    _leveledDfs(1, parent, level, rtr_lvl, st, sol, crt_edg);

    return pair<int, vector<vector<int>>> (sol.size(), sol); // return (numer of biconex components, sol[i] - vertex's of component)
}

vector<vector<int>> Graph::CriticalConnections()
{
    /// Critical edges of a connected graph
    // With a leveled DF find critical edges
    vector<vector<int>> sol;
    vector<int> parent;
    vector<int> level;
    vector<int> rtr_lvl;
    stack<Edge> st;
    vector<vector<int>> crt_edg;

    _leveledDfs(0, parent, level, rtr_lvl, st, sol, crt_edg);
    return crt_edg; // crt_edg[i] => pair start node - end node of critical edge
}

pair <int, vector<vector<int>>> Graph::StronglyConnectedComponents()
{
    /// Strongly connected components of oriented graph (number and components)
    vector<int> in_time(_nr_vertex + 1, -1);
    vector<int> return_time(_nr_vertex +1, -1);
    stack<int> connection;
    vector<bool> in_connection(_nr_vertex + 1, 0);
    int time;
    time = 0;
    int sol_nr = 0;
    vector<vector<int>> sol;

    for (int i = 1; i <= _nr_vertex; ++i)
    {
        if (in_time[i] == -1)
            _Tarjan(i, time, in_time, return_time, connection, in_connection, sol_nr, sol);
    }

    return make_pair(sol_nr, sol);
}

vector<int> Graph:: BellmanFord(const int& start)
{
    /// Bellman Ford algorithm for determining the minimum cost from start to the other nodes
    /// and detecting the negative cycles
    // returning vector of costs if there's a negative cycle return is -1
    if (!_weighted || start < 1 || start > _nr_vertex)
       return vector<int>(1,-1);

    vector<int> dist(_nr_vertex + 1, INT_MAX); // assume we cant get to any node
    vector<int> parent(_nr_vertex + 1, -1);
    queue<int> order;
    vector<bool> in_q(_nr_vertex + 1, 0);
    vector<int> count_in_q(_nr_vertex  + 1, 0); // how many times was N in queue

    int node;
    dist[start] = 0;
    in_q[start] = 1; // mark that the current node is in queue
    count_in_q[start] = 1;
    order.push(start);

    while(!order.empty())
    {
       node = order.front();
       order.pop();
       in_q[node] = 0; // mark that it was popped out of the queue

        for (auto &neighbour:_adjacency[node]) // go through neighbors
        {
            if (dist[neighbour.to] > dist[node] + neighbour.weight) // we can actualize the distance from node to neighbor
            {
                dist[neighbour.to] = dist[node] + neighbour.weight;
                parent[neighbour.to] = node; // mark from who we got to neighbor
                if (!in_q[neighbour.to])
                {
                     // if the node was in queue more times then nodes there is a negative cycle
                     if (count_in_q[neighbour.to] > _nr_vertex)
                        return vector<int>(1,-1);
                    order.push(neighbour.to);
                    count_in_q[neighbour.to] += 1;
                }
            }
        }
    }

    return dist;
}

tuple<int, int, vector<Edge>> Graph::MinimumSpanningTree()
{
    ///Using Prim's Algorithm return the cost of MST, number of edges (nr vertex - 1) and the actual edges
    if (!_weighted)
        return make_tuple(-1,-1,vector<Edge>());
    pair<int, vector<Edge>> solution = Prim(1);
    return make_tuple(solution.first, solution.second.size(), solution.second);
}

void Graph::HavelHakimi(istream &in)
{
    /// Solving the problem "determine if array of degrees is a valid non-oriented graph" with Havel Hakimi theorem
    /// and making the graph. Destroys current graph.
    if (_oriented)
        return;

    bool breakflag = 0; // flag in case the algorithm should end so we can display first
    int aux, n, m = 0, nr_zeros = 0;
    vector<pair<int,int>> degrees; // <node, degree>

    in >> n;
    for (int i = 1; i <= n; ++i)
    {
        in >> aux;
        // if degree > number of vertex's => impossible
        if (aux > n)
        {
            breakflag = 1;
            break;
        }
        if (aux == 0)
            nr_zeros++;

        m += aux;
        degrees.push_back(make_pair(i, aux));
    }
    if  (m % 2 == 1 || breakflag )
        cout << "Not a graph.\n";
    else if (nr_zeros == n)
    {
        resize(n);
        cout <<"An empty graph with " << n << " vertex's.\n";
    }
    else
     {
            bool answer = _HavelHakimi(n,m,degrees);
            if  (!answer)
            {
                clear();
                cout << "Not a graph.\n";
            }
            else
            {
                cout << *this;
            }
     }
}

vector<int> Graph::EulerianCycle()
{
    /// Determine Eulerian cycle of graph and return the nodes component
    /// if there isn't any return -1
    // Reunion of disjunct cycles

    //make another adjacency list not to destroy current one (+O(m) time and O(n^2) memory)
    // but it is more reasonable that the graph is not destroyed when calling a method.
    vector<vector<int>> adj_list;
    _initializeAdMat(adj_list);

    for (int i = 1; i <= _nr_vertex; ++i)
    {
        if (adj_list[i].size() % 2 == 1) // theorem
            return vector<int>(1, -1);
    }

    stack<int> nodes; // stack of nodes to make recursive alg. iterative
    nodes.push(1); // start from whatever node (cycle theory)

    vector<int> cycle; // solution
    int current, next_node, nr = 0;

    while (!nodes.empty())
    {
        current = nodes.top(); // take the current node

        if (nr == _nr_edge) // got Eulerian cycle so break loop
            break;
        if (adj_list[current].empty()) // passed through all neighbors
        {
            nr++; // mark nr of edges used
            nodes.pop();
            cycle.push_back(current); // add node to cycle
        }
        else
        {
                next_node = adj_list[current].back(); // get a random neighbor
                adj_list[current].pop_back(); // remove the edge from current node

                auto pos = find(adj_list[next_node].begin(), adj_list[next_node].end(), current);
                if (pos != adj_list[next_node].end())
                    adj_list[next_node].erase(pos); // remove the edge from the other node side
                nodes.push(next_node); // add node to recursion
        }
    }

    if (nr != _nr_edge) // there's no cycle.
        return vector<int> (1, -1);

    return cycle;
}

bool Graph::VerifyHavelHakimi(const int& n, const int& nr_d, vector<pair<int,int>>& degrees)
{
    /// Just verifies the theorem
    int  m = nr_d/2; // number of edges is sum of degrees/2

    vector<vector<bool>> matrix_adj; // access to finding if a edge exists in O(1). but memory + O(n^2)
    matrix_adj.resize(n + 1, vector<bool>(n + 1, 0));

    pair<int,int> max_dg;
    while (m != 0) //the max nr. of iterations is the number of edges in the supposed graph
    {
        sort(degrees.begin(), degrees.end(), SortDescPair); // sort the degrees descending
        if (degrees.size() == 0 || degrees[0].second == 0)
            break; // no vertex left with degree > 0

        max_dg.first = degrees[0].first;
        max_dg.second = degrees[0].second;
        degrees.erase(degrees.begin());

        for (unsigned int k = 0; k < degrees.size() && max_dg.second > 0 ; ++k)
        {
            if (matrix_adj[max_dg.first][degrees[k].first] != 0)
            {
                matrix_adj[max_dg.first][degrees[k].first] = 1; // mark added edge
                matrix_adj[degrees[k].first][max_dg.first] = 1;

                degrees[k].second--; // mark added edge in degrees left
                max_dg.second--;

                if (degrees[k].second < 0) // can't add edges
                    return 0;

                m--;
            }
        }

        if (max_dg.second != 0) // didn't add the required edges
            return 0;
    }

    if (m != 0) return 0; // Couldn't add the number of desired edges so not a valid graph

    return 1;
}

/// Pentru meniu
class Infoarena
{
protected:
    Infoarena()
    {
    }

    static Infoarena* instance;

    bool check(ifstream& in)
    {
        if (!in.good())
        {
            cout << "Verificati daca fisierul de intrare exista.\n";
            in.close();
            return false;
        }
        return true;
    }

    void showRead(const char* filename)
    {
        cout << "Citire date din \""<< filename <<".in\".\n";
    }

    void showEnd(const char* filename)
    {
        cout << "Verificati fisierul de iesire \"" << filename <<".out\".\n";
    }

public:
    Infoarena(Infoarena&) = delete;
    void operator=(const Infoarena&) = delete;

    static Infoarena* init()
    {
        if (instance == NULL)
            instance = new Infoarena();
        return instance;
    }

    void havelhakimi()
    {
        Graph g;
        g.HavelHakimi(cin);
    }

    void bfs()
    {
        showRead("bfs");
        ifstream in("bfs.in");
        if (!check(in))
            return;

        int n, m, s, x, y;
        in >> n >> m >> s;
        Graph g(1);
        g.resize(n);
        for (int i = 1; i <=m; ++i)
        {
            in >> x >> y;
            g.AddEdge(x,y);
        }
        vector<int> sol = g.DistanceFrom(s);
        ofstream out("bfs.out");
        for (unsigned int i = 0; i < sol.size(); ++i)
            out << sol[i] << " ";
        out.close();
        showEnd("bfs");
    }

    void dfs()
    {
        showRead("dfs");
        ifstream in("dfs.in");
        if (!check(in))
            return;
        Graph g;
        in >> g;
        in.close();
        int sol = g.NrConnectedComponents();

        ofstream out("dfs.out");
        out << sol;
        out.close();
        showEnd("dfs");
    }

    void sortaret()
    {
        showRead("sortaret");
        ifstream in("sortaret.in");
                if (!check(in))
            return;

        Graph g(1);
        in >> g;
        in.close();

        vector<int> sol = g.TopologicalSort();
        ofstream out("sortaret.out");
        for (unsigned int i = 0; i < sol.size(); ++i)
            out << sol[i] << " ";
        out.close();
        showEnd("sortaret");
    }

    void biconex()
    {
        showRead("biconex");
        ifstream in("biconex.in");
        if (!check(in))
            return;

        Graph g;
        in >> g;
        in.close();

        ofstream out("biconex.out");
        pair<int, vector<vector<int>>> sol = g.BiconnectedComponents();
        out << sol.first << "\n";

        for (int i = 0; i < sol.first; ++i)
        {
            sort(sol.second[i].begin(), sol.second[i].end());
            sol.second[i].erase(unique(sol.second[i].begin(), sol.second[i].end()), sol.second[i].end());
            for (unsigned int e_idx = 0; e_idx < sol.second[i].size(); ++e_idx)
            {
                out << sol.second[i][e_idx] << " ";
            }
            out << "\n";
        }
        out.close();
        showEnd("biconex");
    }

    void ctc()
    {
        showRead("ctc");
        ifstream in("ctc.in");
        if (!check(in))
            return;
        Graph g(1);
        in >> g;
        in.close();
        pair<int, vector<vector<int>>> solution = g.StronglyConnectedComponents();
        ofstream out("ctc.out");
        out << solution.first << endl;
        for (int i = 0; i < solution.first; ++i)
        {
            for (auto &it: solution.second[i])
                out << it << " ";
            out << "\n";
        }
        out.close();
        showEnd("ctc");
    }

    void leetcc()
    {
        showRead("criticalconnections");
        ifstream in("criticalconnections.in");
         if (!check(in))
            return;
        ofstream out("criticalconnections.out");
        Graph g;
        in >> g;
        in.close();
        vector<vector<int>> crt_edg = g.CriticalConnections();

        for (unsigned int i = 0; i < crt_edg.size(); ++i)
        {
            for (unsigned int e_idx = 0; e_idx < crt_edg[i].size(); ++e_idx)
            {
                out << crt_edg[i][e_idx] << " ";
            }
            out << "\n";
        }
        out.close();
        showEnd("criticalconnections");
    }

    void apm()
    {
        showRead("apm");
        ifstream in("apm.in");
         if (!check(in))
            return;
        Graph g(0,1);
        in >> g;
        in.close();
        ofstream out("apm.out");
        tuple<int,int, vector<Edge>> sol = g.MinimumSpanningTree();
        out << get<0>(sol) << "\n" << get<1>(sol) << "\n";

        for (auto &edge:get<2>(sol))
        {
            out << edge.from << " " << edge.to << "\n";
        }

        out.close();
        showEnd("apm");
    }

    void dijkstra()
    {
        showRead("dijkstra");
        ifstream in("dijkstra.in");
         if (!check(in))
            return;
        Graph g(1,1);
        in >> g;
        in.close();
        vector<int> solution = g.Dijkstra(1);
        ofstream out("dijkstra.out");
        int n = g.nrVertex();
        for (int i = 2;  i < n + 1 ; ++i)
        {
            if (solution[i] == INT_MAX) out << 0 << " ";
            else out << solution[i] << " ";
        }
        out.close();
         showEnd("dijkstra");
    }

    void disjoint()
    {
        showRead("disjoint");
        ifstream in("disjoint.in");
         if (!check(in))
            return;
        int n, m;
        in >> n >> m;

        vector<Forest> f = Forest::MakeForest(n);
        int x, y, cod;

        ofstream out("disjoint.out");
        while (m)
        {
            m--;

            in >> cod >> x >> y;
            if (cod == 1)   Forest::UnionForest(x, y, f);
            if (cod == 2)
            {
                if (Forest::CheckForest(x,y,f) == 1)
                    out << "DA\n";
                else out  << "NU\n";
            }
        }
        out.close();
        showEnd("disjoint");
    }

    void bellmanford()
    {
        showRead("bellmanford");
        ifstream in("bellmanford.in");
        if (!check(in))
            return;
        Graph g(1,1);
        in >> g;
        in.close();
        ofstream out("bellmanford.out");
        vector<int> sol = g.BellmanFord(1);
        if (sol.size() == 1 && sol[0] == -1)
            out << "Ciclu negativ!\n";
        else
        {
            for (int i = 2; i <= g.nrVertex(); ++i)
                out << sol[i] << " ";
        }
        out.close();
        showEnd("bellmanford");
    }

    void maxflow()
    {
        showRead("maxflow");
        ifstream in("maxflow.in");
        if (!check(in))
            return;
        Graph g(1, 0, 1);
        in >> g;
        in.close();
        ofstream out("maxflow.out");
        out << g.MaxFlow(1, g.nrVertex());
        out.close();
        showEnd("maxflow");
    }

    void royfloyd()
    {
        showRead("royfloyd");
        ifstream in("royfloyd.in");
        if (!check(in))
            return;
        int n;
        in >> n;
        Graph g(1, 1);
        g.resize(n);
        vector<vector<int>> sol (n + 1, vector<int> (n+1, INT_MAX));
        for (int i = 1; i <= n; ++i)
        {
            for (int j = 1; j <= n; ++j)
            {
                in >> sol[i][j];
                if (sol[i][j] == 0 && i != j)
                    sol[i][j] = INT_MAX;
            }
        }
        in.close();
        g.RoyFloyd(sol);
        ofstream out("royfloyd.out");
        for (int i = 1; i <= n; ++i)
        {
            for (int j = 1; j <= n; ++j)
            {
                if (sol[i][j] == INT_MAX)
                    out << 0 << " ";
                else out << sol[i][j] << " ";
            }
            out <<"\n";
        }
        out.close();
        showEnd("royfloyd");
    }

    void darb()
    {
        showRead("darb");
        ifstream in("darb.in");
        if (!check(in))
            return;
        int n, x, y;
        Graph g;
        in >> n;
        g.resize(n);
        for (int i = 1; i <= n - 1; ++i)
        {
            in >> x >> y;
            g.AddEdge(x,y);
        }
        in.close();
        ofstream out("darb.out");
        out << g.Diameter();
        out.close();
        showEnd("darb");
    }

    void ciclueuler()
    {
        showRead("ciclueuler");
        ifstream in("ciclueuler.in");
        if (!check(in))
            return;
        Graph g;
        in >> g;
        in.close();
        ofstream out("ciclueuler.out");
        vector<int> sol = g.EulerianCycle();
        if (sol.size() == 1 && sol[0] == -1)
            out << "-1\n";
        else
        {
            for (auto &node: sol)
                out << node << " ";
        }
        out.close();
        showEnd("ciclueuler");
    }
};

Infoarena *Infoarena::instance = NULL;
Infoarena *probleme = probleme->init();

void displayOption(const int& i, const char* option)
{
    cout << i << ")\tProblema " << option << "\n";
}

void meniu()
{
     int code = 0;

    for (int i = 1; i <= 16; ++i)
    {
        switch (i)
        {
            case 16: cout <<"0 for EXIT\n"; break;
            case 1: cout << "Tema \"BF-DF si aplicatii\":\n"; displayOption(i, "bfs"); break;
            case 2:  displayOption(i, "dfs"); break;
            case 3:  displayOption(i, "sortare topologica"); break;
            case 4:  displayOption(i, "biconex"); break;
            case 5:  displayOption(i, "ctc"); break;
            case 6:  displayOption(i, "Havel Hakimi"); break;
            case 7: displayOption(i, "critical connections (leetcode)"); break;
            case 8: cout << "\nTema \"Drumuri minime si APM\":\n"; displayOption(i, "apm"); break;
            case 9: displayOption(i, "Dijkstra"); break;
            case 10: displayOption(i, "disjoint"); break;
            case 11: displayOption(i, "Bellman-Ford"); break;
            case 12: cout << "\nTema pentru laboratorul 5\n"; displayOption(i, "maxflow"); break;
            case 13:  displayOption(i, "Roy-Floyd"); break;
            case 14: displayOption(i, "darb"); break;
            case 15: cout << "\nTema pentru laboratorul 6\n"; displayOption(i, "Ciclu Eulerian"); break;
        };
    }

    do
    {
        cout << "Optiune: ";
        cin >> code;
        switch (code)
        {
            case 1: probleme->bfs(); break;
            case 2: probleme->dfs(); break;
            case 3: probleme->sortaret(); break;
            case 4: probleme->biconex(); break;
            case 5: probleme->ctc(); break;
            case 6: probleme->havelhakimi(); break;
            case 7: probleme->leetcc(); break;
            case 8: probleme->apm(); break;
            case 9: probleme->dijkstra(); break;
            case 10: probleme->disjoint(); break;
            case 11: probleme->bellmanford(); break;
            case 12: probleme->maxflow(); break;
            case 13: probleme->royfloyd(); break;
            case 14: probleme->darb(); break;
            case 15: probleme->ciclueuler(); break;
            case 0: return; break;
            default: cout << "Optiune invalida.\n";
        };
    } while(code != 0);
}

int main()
{
        ifstream in("dfs.in");
        //if (!check(in))
            //return;
        Graph g;
        in >> g;
        in.close();
        int sol = g.NrConnectedComponents();

        ofstream out("dfs.out");
        out << sol;
        out.close();
        //showEnd("dfs");
    /*
    // Se poate accesa meniu pentru lista de probleme rezolvate
    // la teme. Sau a se scrie un program propriu ce foloseste
    // clasa Graph.

    Graph g(1);
    cin >> g;
    vector<int> bfs = g.BF(1);

    for (auto elem: bfs)
        cout << elem << " ";
    cout << endl;

    vector<int> dfs = g.DF(1);

    for (auto elem: dfs)
        cout << elem << " ";
    cout << endl;

    cout << g.MaxFlow(1,4) << endl;

    g.MakeFlowNetwork();
    cin >> g;

    cout << g.MaxFlow(1, 4) << endl;
    */
    return 0;
}