UVa – 11747 – Heavy Cycle Edges

Problem Link: https://onlinejudge.org/external/117/11747.pdf

Note: This problem is a variation of the Minimum Spanning Tree(MST) and it gives a deeper understanding of Krushkal’s algorithm.

We know that we need to run the Union-Find algorithm in Krushkal’s and we run that up to (node – 2) edges. Because we just need to check – starting from any node, whether we can reach any node or not. For example: if there are 3 nodes in a graph – 1,2,3. Then having edges between 1-2-3 would be enough and will form an MST. No direct edge between 1-3 is required.

But to solve this problem, we’ll need to check up to (node – 1) edges, to check for a cycle. That’s the main trick!

Code:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <algorithm>
#include <new>
#include <map>
#include <unordered_map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#include <climits>

using namespace std;

int Find(int x, vector<int> &root)
{
    if(root[x]==x) return x;
    return root[x]=Find(root[x], root);
}

void Union(int x, int y, vector<int> &root, vector<int> &ranking)
{
    int rootX=Find(x, root);
    int rootY=Find(y, root);
    if(rootX!=rootY)
    {
        if(ranking[rootX]>=ranking[rootY]) root[rootY]=rootX;
        else if(ranking[rootY]>=ranking[rootX]) root[rootX]=rootY;
        else
        {
            root[rootY]=rootX;
            ranking[rootX]++;
        }
    }
}

vector<int> Krushkals(vector<pair<int, pair<int, int> > > &edges, int node)
{
    sort(edges.begin(), edges.end());
    vector<int> root(node);
    vector<int> ranking(node);
    for(int i=0; i<node; i++)
    {
        root[i]=i;
        ranking[i]=1;
    }
    int edge_added=0;
    vector<int> ans;
    int maxi=INT_MIN;
    for(int i=0; i<edges.size() && edge_added<node; i++)
    {
        int x=edges[i].second.first;
        int y=edges[i].second.second;
        int cost=edges[i].first;
        if(Find(x, root)!=Find(y, root)) //if no cycle exists
        {
            Union(x,y,root, ranking);
            edge_added++;
            maxi=max(maxi, cost);
        }
        else
        {
            
            maxi=max(maxi, cost);
            ans.push_back(maxi);
            maxi=INT_MIN;
        }
    }
    return ans;

}

int main()
{
    int node,edge;
    while(cin>>node>>edge)
    {
        if(node==0 && edge==0) break;
        vector<pair<int, pair<int, int> > > edges;

        for(int i=0; i<edge; i++)
        {
            int x,y,cost;
            cin>>x>>y>>cost;
            edges.push_back({cost, {x, y}});
        }
        vector<int> ans=Krushkals(edges, node);
        if(ans.size()==0) cout<<"forest"<<endl;
        else
        {
            for(int i=0; i<ans.size()-1; i++) cout<<ans[i]<<" ";
            cout<<ans[ans.size()-1]<<endl;
        }
    }
    return 0;
}

UVa – 11631 – Dark Roads

Problem Link: https://onlinejudge.org/external/116/11631.pdf

Note: This problem is a simple implementation of the Minimum Spanning Tree(MST) with a very simple tweak. I applied Prim’s algorithm here, Krushkal’s algorithm can also be used.

Code:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <algorithm>
#include <new>
#include <map>
#include <unordered_map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>


using namespace std;

int Prims(vector<vector<pair<int, int> > > &edges, int node)
{
    priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > >pq;
    int vis[200005]= {0};
    int ans=0;
    int nodes_added=0;
    pq.push({0,0});
    while(nodes_added<node)
    {
        int from=pq.top().second;
        int cost=pq.top().first;
        pq.pop();
        if(!vis[from])
        {
            nodes_added++;
            vis[from]=1; //added to MST
            ans+=cost;
            for(int i=0; i<edges[from].size(); i++)
            {
                pair<int,int> to=edges[from][i];
                pq.push(to);
            }
        }
    }
    return ans;
}

int main()
{
    int node, edge;
    while(cin>>node>>edge)
    {
        if(node==0 && edge==0) break;
        vector<vector<pair<int, int> > > edges(node);
        int total_cost = 0;
        for(int i=0; i<edge; i++) //Building adjacency graph
        {
            int x,y,cost;
            cin>>x>>y>>cost;
            edges[x].push_back({cost, y});
            edges[y].push_back({cost, x});
            total_cost+=cost;
        }
        int ans=Prims(edges, node);
        cout<<total_cost-ans<<endl;
    }
    return 0;
}

UVa – 10557 – xyzzy

Problem Link: https://onlinejudge.org/external/105/10557.pdf

Hints:

Here are some important understandings to pick up from the problem statement while attempting to solve the problem. I struggled to some extent with catching up on the problem due to these facts πŸ˜“

  1. Since the problem is directly related to energy consumption, we need to find the path with maximum cost(energy).
  2. Another important factor is, here cost is not the edge cost. It’s the node’s cost. Notice that, it’s mentioned in the problem statement – “The energy value of this room is added to the player’s energy.” So, the usual edge cost storage of Bellman-Ford will not be implemented in this problem. So to track each node’s cost(rather than edge cost), I have taken a separate 1D vector named room_energy.
  3. At any time while traveling, if the energy becomes <=0, and there is no other path, then immediately the result would be “hopeless” because it is not possible to go to another room with equal or less than 0 energy.
  4. If the destination node can’t be reached using each edge once with >0 energy, then check for a positive cycle with the help of which(traversing that cycle repeatedly -> increasing energy enough to reach the destination node with >0 energy) the destination node can be reached with >0 energy.
  5. While taking the edges as input, it would be wrong to assume that – “Always the last line of a particular test case would be 0 0. That is if the energy value for room i and the number of doorways leaving room i both are 0, then we should break the loop, that is the end of input taking for that case” – no, that’s not the case. Because any room i can have an energy value 0 and also no room can be reached from i. So even the first or second room can have 0 0 cases (0 energy, 0 rooms connected).

My solution idea and approach:

It’s evident that we’ll have to use Bellman-Ford here. So is only Bellman-Ford enough to solve this problem? πŸ€” Let’s see.

We’ll have to start from node 1 and will have to reach node n. Also, after reaching the destination node n, the energy cannot be 0. Obviously, before reaching node n, energy can’t be 0, if so, the programs generate “hopeless” immediately.

Now, we’ll have to think, is node n connected to node 1? It’s very simple, only running Bellman-Ford will tell us that. How? While running Bellman-Ford, lastly we’ll return dis[n]. If the value is not updated from 0, that means that was not reached. Though the dis[n] can be 0 not just for the disconnectivity, but also due to total_energy being 0 after reaching node n. But that is not our concern how dis[n] is 0, as we’ll lastly check if dis[n] is not 0, then “winnable” should be printed.

Now, let’s consider the possibility of cycle/loop in the graph as a general Bellman-Ford would consider. In this problem, already it is evident that we would need to consider the positive cycles only. Because that can give us infinite energy(by repeatedly traversing the cycle) to reach the destination if and only if any node of the positive cycle is connected to the destination. Notice that, it is mentioned in the problem that we can repeat any path any number of times.

So two kinds of connectivity to check-

  1. Whether node 1 is connected to node n. It doesn’t matter if any other nodes are not connected. For example, for n=5, a graph can be 1-3-5 2-4. Assuming energies between 1-3-5 are all >0, the output to this input would be “winnable”. Now, even if node 1 and node n are connected, the output would be “hopeless” if the only path to reach from 1 to n would result in <=0 energy. So for that scenario, we’ll have to check for a positive cycle.
  2. If there is a positive cycle, whether any node of the cycle is connected to the destination node n. If that’s the case, then the output will definitely be “winnable”.

Now, the most intuitive idea can be –

  1. At first, we’ll have to check whether node 1 is connected and reachable to node n or not. If yes, then winnable
  2. If step 1 is NO, then we’ll have to look for positive cycles and then whether that cycle is connected to n. If yes, then winnable, otherwise hopeless.

If we think like this, then to address the first part, Bellman-Ford is enough by checking the value of dis[n]. For the 2nd part, we can detect a positive cycle inside the Bellman-Ford. But what about checking the connectivity of that cycle to the node n? One way could be – while checking the existence of the positive cycle inside Bellman-Ford, then store the nodes involved in the cycle in a set. And then checking for each node separately, whether those nodes are connected to the node n or not using DFS; that is to run the DFS code k th time if there are k nodes involved in the positive cycle. But this process seems much time-consuming, right? There might be a better way. πŸ€”

Let’s interchange the above 2 steps to solve the problem! πŸ’‘

  1. At first, we’ll mark the nodes which are reachable from the destination node n using DFS.
  2. Then we’ll use Bellman-Ford to check whether node 1 is connected to node n with >0 energy or not. Also simultaneously will look for positive cycles and then whether that cycle is connected to the destination node n. If any of the answers to these two simultaneous things is yes, then winnable, otherwise hopeless.

So in this approach, we are doing the same thing, just with less time-consuming than the previous approach 😊

Steps in a nutshell:

  1. While creating the adjacency list adj for the input graph, create another adjacency list named adj_reverse. We’ll need that graph to mark the nodes which are reachable from the destination node.
  2. Run DFS on adj_reverse to mark the nodes which are reachable from the destination node.
  3. Run Bellman-Ford. If the destination node n is reachable from node 1 with >0 energy (dis[n] will give the result) or if there is a positive cycle and any node of the positive cycle is already visited (which we got from running the DFS on adj_reverse) and we return true immediately, that means the output would be “winnable”. Otherwise (the return is not true and dis[n] is <=0) the output would be “hopeless”.

Code:

#include <bits/stdc++.h>

using namespace std;

void dfs(int u, vector<vector<int> > &adj_reverse, vector<bool> &vis)
{
    vis[u]=1; // storing the connected nodes to the source node
    for(int i=0; i<adj_reverse[u].size(); i++)
    {
        int v=adj_reverse[u][i];
        if(!vis[v]) dfs(v, adj_reverse, vis);
    }
}

int bellman_ford(int node, vector<vector<int> > &adj, vector<int> &room_energy, vector<bool> &vis)
{
    vector<int> dis(node+1, 0);
    dis[1]=100;

    for(int i=0; i<node; i++)
    {
        for(int u=1; u<=node; u++)
        {
            for(int k=0; k<adj[u].size(); k++)
            {
                int v=adj[u][k];
                int cost=room_energy[v];

                if(dis[u] && dis[v]<dis[u]+cost)
                {
                    dis[v]=dis[u]+cost;
                }
            }
        }
    }

    //For checking positive cycle
    for(int u=1; u<=node; u++)
    {
        for(int k=0; k<adj[u].size(); k++)
        {
            int v=adj[u][k];
            int cost=room_energy[v];

            if(dis[u] && dis[v]<dis[u]+cost)
            {
                //whether any node of the positive cycle is connected to the destination node or not
                if(vis[v]) return true;
            }
        }
    }
    return dis[node];
}

int main()
{
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);

    int n;
    while(cin>>n && n!=-1)
    {
        vector<vector<int> > adj(n+1), adj_reverse(n+1);
        vector<int> room_energy(n+1);
        for(int i=1; i<=n; i++)
        {
            int cost, from=i, num;
            cin>>cost>>num;

            room_energy[i]=cost;

            for(int j=0; j<num; j++)
            {
                int to;
                cin>>to;
                adj[from].push_back(to);
                adj_reverse[to].push_back(from);
            }
        }

        vector<bool> vis(n+1, 0);
        dfs(n, adj_reverse, vis); //Marking the connected nodes to the destination node in vis[]

        int result=bellman_ford(n, adj, room_energy, vis); //checking for cycle and the reachability
        if(result) cout<<"winnable"<<endl;
        else cout<<"hopeless"<<endl;
    }

    return 0;
}

Bellman-Ford with Path Print

Note: This is the basic algorithm of Bellman-Ford in addition to the Parent vector and the path_print function.

#include <bits/stdc++.h>

#define pii pair<int,int>

using namespace std;

void path_print(int dst, vector<int> &parent)
{
    if(dst==-1) return;
    path_print(parent[dst], parent);
    cout<<dst<<" ";
}

vector<long long int> bellman_ford(int node, int edge, vector<pair<pii, int> > &adj,  vector<int> &parent)
{
    vector<long long int> dis(node, INT_MAX);
    dis[0]=0;
    parent[0]=-1;

    for(int i=0; i<node; i++)
    {
        for(int j=0; j<edge; j++)
        {
            int u=adj[j].first.first;
            int v=adj[j].first.second;
            int cost=adj[j].second;
            if(dis[u] != INT_MAX && dis[v] > dis[u]+cost)
            {
                dis[v]=dis[u]+cost;
                parent[v]=u;
            }
        }
    }

    ///Running the loop one more time to find out whether the graph contains any negative cycle loop or not

    //bool neg_cycle=0; //To detect whether any negative cycle present in the graph or not
    for(int j=0; j<edge; j++)
    {
        int u=adj[j].first.first;
        int v=adj[j].first.second;
        int cost=adj[j].second;
        if(dis[u] != INT_MAX && dis[v] > dis[u]+cost)
        {
            /*
            neg_cycle=1;
            break;
            */
            dis[v]=INT_MAX; //Marking the nodes which will keep decreasing infinitly due to negative weight cycle
        }
    }
    return dis;
}


int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);

    int node,edge;
    cout<<"Enter the number of nodes: ";
    cin>>node; //assuming 0<=node
    cout<<"Enter the number of edges: ";
    cin>>edge;

    vector<pair<pii, int> > adj;


    for(int i=0; i<edge; i++)
    {
        int from, to, cost;
        cin>>from>>to>>cost;
        adj.push_back({{from, to}, cost});
    }

    //Taking "long long int" instead of "int" because adding up costs can cause overflow of integer
    vector<int> parent(node);
    vector<long long int> dis=bellman_ford(node, edge, adj, parent);


    int q;
    cout<<"Enter how many queries you want to make: ";
    cin>>q;
    for(int i=0; i<q; i++)
    {
        int dst;
        cout<<"Enter the destination: ";
        cin>>dst;
        if(dis[dst]==INT_MAX)
        {
            cout<<"Due to some negative cycle, this destination can't be reached."<<endl;
        }
        else
        {
            cout<<"The lowest cost is: "<<dis[dst]<<endl;
            cout<<"And the path is: ";
            path_print(dst, parent);
        }
    }

    return 0;
}

Bellman-Ford

Code:

#include <bits/stdc++.h>

#define pii pair<int,int>

using namespace std;

vector<long long int> bellman_ford(int node, int edge, vector<pair<pii, int> > &adj)
{
    vector<long long int> dis(node, INT_MAX);
    dis[0]=0;

    for(int i=0; i<node; i++)
    {
        for(int j=0; j<edge; j++)
        {
            int u=adj[j].first.first;
            int v=adj[j].first.second;
            int cost=adj[j].second;
            if(dis[u] != INT_MAX && dis[v] > dis[u]+cost)
            {
                dis[v]=dis[u]+cost;
            }
        }
    }

    ///Running the loop one more time to find out whether the graph contains any negative cycle loop or not

    //bool neg_cycle=0; //To detect whether any negative cycle present in the graph or not
    for(int j=0; j<edge; j++)
    {
        int u=adj[j].first.first;
        int v=adj[j].first.second;
        int cost=adj[j].second;
        if(dis[u] != INT_MAX && dis[v] > dis[u]+cost)
        {
            /*
            neg_cycle=1;
            break;
            */
            dis[v]=INT_MAX; //Marking the nodes which will keep decreasing infinitly due to negative weight cycle
        }
    }
    return dis;
}


int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);

    int node,edge;
    cout<<"Enter the number of nodes: ";
    cin>>node; //assuming 0<=node
    cout<<"Enter the number of edges: ";
    cin>>edge;

    vector<pair<pii, int> > adj;


    for(int i=0; i<edge; i++)
    {
        int from, to, cost;
        cin>>from>>to>>cost;
        adj.push_back({{from, to}, cost});
    }

    //Taking "long long int" instead of "int" because adding up costs can cause overflow of integer
    vector<long long int> dis=bellman_ford(node, edge, adj);


    int q;
    cout<<"Enter how many queries you want to make: ";
    cin>>q;
    for(int i=0; i<q; i++)
    {
        int dst;
        cout<<"Enter the destination: ";
        cin>>dst;
        if(dis[dst]==INT_MAX)
        {
            cout<<"Due to some negative cycle, this destination can't be reached."<<endl;
        }
        else
        {
            cout<<dis[dst]<<endl;
        }
    }

    return 0;
}