diff --git a/Submissions/Sinchana_Kumara_002780971/Assignment_4.ipynb b/Submissions/Sinchana_Kumara_002780971/Assignment_4.ipynb new file mode 100644 index 0000000..d5638dd --- /dev/null +++ b/Submissions/Sinchana_Kumara_002780971/Assignment_4.ipynb @@ -0,0 +1,1756 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# **Assignment 4**" + ], + "metadata": { + "id": "_pJK8g3QoZGr" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 1:**\n", + "\n", + "Consider the following graph-related problem:\n", + "\n", + "Given an undirected graph G=(V,E), a dominating set is a subset D of V such that every vertex in V is either in D or adjacent to a vertex in D. The dominating set problem is to determine whether a graph has a dominating set of size at most k, where k is a given positive integer.\n", + "\n", + "1. Is the dominating set problem in P? If so, prove it.\n", + "2. Suppose we impose the additional constraint that the dominating set must be an independent set, meaning no two vertices in the set are adjacent. We call this the independent dominating set problem. Is the independent dominating set problem in NP? If so, prove it.\n", + "3. Is the independent dominating set problem NP-complete? If so, prove it.\n", + "\n" + ], + "metadata": { + "id": "ojI1x7TLod_d" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 1:**\n", + "\n", + "**1. Is the dominating set problem in P? If so, prove it.**\n", + "\n", + "The dominating set problem is NP-complete. It is not known to be in P, and proving that it is in P would imply P = NP, which is an unsolved problem in computer science.\n", + "\n", + "**2. Suppose we impose the additional constraint that the dominating set must be an independent set, meaning no two vertices in the set are adjacent. We call this the independent dominating set problem. Is the independent dominating set problem in NP? If so, prove it.**\n", + "\n", + "Yes, the independent dominating set problem is in NP. Given a set of vertices, we can easily verify in polynomial time whether it forms an independent dominating set. We need to check two conditions: (1) No two vertices in the set are adjacent (independent set property), and (2) every vertex in the graph is either in the set or adjacent to a vertex in the set (dominating set property). Both conditions can be verified in polynomial time.\n", + "\n", + "**3. Is the independent dominating set problem NP-complete? If so, prove it.**\n", + "\n", + "The independent dominating set problem is NP-complete. To prove this, we can reduce the known NP-complete problem, the \"vertex cover\" problem, to the independent dominating set problem. The reduction is as follows:\n", + "\n", + "Given an undirected graph G=(V,E), construct a new graph G ′ =(V ′ ,E ′ )where V ′ =V and\n", + "\n", + "E\n", + "′\n", + " ={(u,v)∣u,v∈V,(u,v)∈\n", + "/\n", + "E}.\n", + "\n", + "In other words,\n", + "G\n", + "′\n", + " is the complement graph of G, where edges not in G are added, and edges in G are removed.\n", + "\n", + "Now, we claim that G has a vertex cover of size at most k if and only if\n", + "G\n", + "′\n", + " has an independent dominating set of size at most k.\n", + "\n", + "The proof involves showing that the existence of a vertex cover in G corresponds to the existence of an independent dominating set in G ′ and vice versa, and this can be done in polynomial time. Therefore, the independent dominating set problem is NP-complete." + ], + "metadata": { + "id": "-adtCGTyr0qN" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function VertexCoverToIndependentDominatingSet(G=(V, E), k):\n", + " // Construct the complement graph G'\n", + " G' = ComplementGraph(G)\n", + "\n", + " // Find an independent dominating set in G'\n", + " independentDominatingSet = FindIndependentDominatingSet(G', k)\n", + "\n", + " return independentDominatingSet\n", + "\n", + "```\n", + "This pseudocode outlines the process of transforming an instance of the vertex cover problem into an instance of the independent dominating set problem.\n" + ], + "metadata": { + "id": "tjKe4mDnzB6J" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 2:**\n", + "\n", + "Given an undirected graph G=(V,E), a Hamiltonian cycle is a cycle that visits every vertex exactly once and returns to the starting vertex. The Hamiltonian cycle problem is to determine whether a graph has a Hamiltonian cycle.\n", + "\n", + "1. Is the Hamiltonian cycle problem in P? If so, prove it.\n", + "2. Suppose we impose the additional constraint that the Hamiltonian cycle must include a specific vertex\n", + "s (specified as part of the input). We call this the Constrained Hamiltonian Cycle problem. Is the Constrained Hamiltonian Cycle problem in NP? If so, prove it.\n", + "3. Is the Constrained Hamiltonian Cycle problem NP-complete? If so, prove it." + ], + "metadata": { + "id": "PM-AiAWh4CMx" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 2:**\n", + "\n", + "**1. Is the Hamiltonian cycle problem in P? If so, prove it.**\n", + "\n", + "No, the Hamiltonian cycle problem is not known to be in P, and it is considered NP-complete. Proving that P equals NP, and that a polynomial-time algorithm exists for the Hamiltonian cycle problem, remains an unsolved problem in computer science.\n", + "\n", + "**2. Suppose we impose the additional constraint that the Hamiltonian cycle must include a specific vertex s (specified as part of the input). We call this the Constrained Hamiltonian Cycle problem. Is the Constrained Hamiltonian Cycle problem in NP? If so, prove it.**\n", + "\n", + "Yes, the Constrained Hamiltonian Cycle problem is in NP. Given a proposed Hamiltonian cycle that includes the specified vertex s, we can efficiently verify its correctness in polynomial time. Verification involves confirming that the cycle visits each vertex exactly once (Hamiltonian) and includes the specified vertex s.\n", + "\n", + "**3. Is the Constrained Hamiltonian Cycle problem NP-complete? If so, prove it.**\n", + "\n", + "To establish the NP-completeness of the Constrained Hamiltonian Cycle problem, we need to show two things:\n", + "\n", + "* Constrained Hamiltonian Cycle is in NP:\n", + "\n", + "Given a proposed solution (a Hamiltonian cycle including s), we can verify its correctness in polynomial time, as explained in part B.\n", + "\n", + "* Constrained Hamiltonian Cycle is NP-hard:\n", + "\n", + "We can demonstrate this by reducing an instance of the Hamiltonian cycle problem to an equivalent instance of the Constrained Hamiltonian Cycle problem. The reduction involves creating a new instance of the Constrained Hamiltonian Cycle problem by specifying a particular vertex s that must be included in the cycle.\n", + "\n", + "Let's assume we have a polynomial-time reduction function f that transforms an instance I of the Hamiltonian cycle problem to an instance I ′ of the Constrained Hamiltonian Cycle problem.\n", + "\n", + "Given an instance I of the Hamiltonian cycle problem, the reduction function f constructs an equivalent instance I ′ of the Constrained Hamiltonian Cycle problem with a specified vertex s.\n", + "\n", + "If we can efficiently solve the Constrained Hamiltonian Cycle problem for I ′, we can apply the inverse of the reduction function to obtain a solution for the Hamiltonian cycle problem on I. Since the reduction is polynomial-time, solving the Constrained Hamiltonian Cycle problem would imply a polynomial-time solution for the Hamiltonian cycle problem, making Constrained Hamiltonian Cycle NP-hard.\n", + "\n", + "Therefore, the Constrained Hamiltonian Cycle problem is NP-complete." + ], + "metadata": { + "id": "IF_jRLUR4LYS" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function HamiltonianCycleToConstrainedHamiltonianCycle(G=(V, E)):\n", + " // Create a new vertex s not in the original graph\n", + " s = new_vertex()\n", + "\n", + " // Add edges from s to all vertices in the original graph\n", + " for each vertex v in V:\n", + " add_edge(s, v)\n", + "\n", + " // Construct the Constrained Hamiltonian Cycle instance with the new vertex s\n", + " G_constrained = (V_union_s, E_union_s)\n", + "\n", + " return G_constrained\n", + "\n", + "```\n", + "\n", + "This pseudocode outlines the process of transforming an instance of the Hamiltonian cycle problem, represented by the graph G=(V,E), into an instance of the Constrained Hamiltonian Cycle problem, represented by the graph constrained\n", + "​\n", + " =(V\n", + "union_s\n", + "​\n", + " ,E\n", + "union_s\n", + "​\n", + " ). The new vertex s is connected to all vertices in the original graph.\n", + "\n", + "\n" + ], + "metadata": { + "id": "_aNdfAPS6NRy" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 3:**\n", + "\n", + "\n", + "The Directed Node-Disjoint Walks Problem:\n", + "\n", + "Given a directed graph G=(V,E) and k pairs of nodes\n", + "(s\n", + "1\n", + "​\n", + " ,t\n", + "1\n", + "​\n", + " ),(s\n", + "2\n", + "​\n", + " ,t\n", + "2\n", + "​\n", + " ),...,(s\n", + "k\n", + "​\n", + " ,t\n", + "k\n", + "​\n", + " ), the problem is to decide whether there exist node-disjoint walks 1,2,...,W 1\n", + "​\n", + " ,W\n", + "2\n", + "​\n", + " ,...,W\n", + "k\n", + "​\n", + " in the graph such that W\n", + "i starts from\n", + "s\n", + "i\n", + " and ends at\n", + "t\n", + "i\n", + " for each i." + ], + "metadata": { + "id": "pAi34hnr9zcb" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 3:**\n", + "\n", + "**1. In NP:**\n", + "\n", + "Given a proposed solution (a set of node-disjoint walks), we can verify its correctness in polynomial time. Verification involves checking that each walk is node-disjoint and satisfies the specified start and end nodes.\n", + "\n", + "**2. NP-Hardness:**\n", + "\n", + "To establish NP-hardness, we need to show a polynomial-time reduction from a known NP-complete problem to the Directed Node-Disjoint Walks Problem. Let's consider reducing the Hamiltonian Path Problem to this problem.\n", + "\n", + "* Reduction:\n", + "\n", + "Given an instance I of the Hamiltonian Path Problem with graph G=(V\n", + "′\n", + " ,E\n", + "′\n", + " ), create an instance I\n", + "′\n", + " of the Directed Node-Disjoint Walks Problem as follows:\n", + "Set k=1 (single pair of nodes).\n", + "Define the pair of nodes as (s,t)=(v\n", + "1\n", + "​\n", + " ,v\n", + "∣V\n", + "′\n", + " ∣\n", + "​\n", + " ) where v\n", + "1\n", + "​\n", + " is the starting node and\n", + "∣\n", + "v\n", + "∣V\n", + "′\n", + " ∣\n", + "​\n", + " is the last node in the Hamiltonian path.\n", + "\n", + "* Claim:\n", + "\n", + "I has a Hamiltonian path if and only if I\n", + "′ has a node-disjoint walk from\n", + "s to t.\n", + "\n", + "* Proof:\n", + "\n", + "If I has a Hamiltonian path, the corresponding node-disjoint walk in I\n", + "′ is the Hamiltonian path itself.\n", + "If I\n", + "′ has a node-disjoint walk from\n", + "s to\n", + "t, it must traverse all nodes from\n", + "s to\n", + "t, corresponding to a Hamiltonian path in\n", + "I.\n", + "\n", + "* Conclusion:\n", + "\n", + "The polynomial-time reduction from the Hamiltonian Path Problem to the Directed Node-Disjoint Walks Problem implies NP-hardness.\n", + "\n", + "By showing both membership in NP and NP-hardness, we establish that the Directed Node-Disjoint Walks Problem is NP-complete." + ], + "metadata": { + "id": "m901dBzBAjAF" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function DirectedNodeDisjointWalks(G=(V, E), pairs=((s1, t1), (s2, t2), ..., (sk, tk))):\n", + " // Create an empty set to store node-disjoint walks\n", + " walks_set = empty_set()\n", + "\n", + " // Iterate over each pair of nodes\n", + " for pair in pairs:\n", + " // Find a node-disjoint walk from si to ti\n", + " walk = FindNodeDisjointWalk(G, pair)\n", + "\n", + " // If a node-disjoint walk is found, add it to the set\n", + " if walk is not empty:\n", + " walks_set.add(walk)\n", + "\n", + " // Check if there is at least one node-disjoint walk for each pair\n", + " return (walks_set is not empty)\n", + "\n", + "function FindNodeDisjointWalk(G=(V, E), pair=(s, t)):\n", + " // Implement a procedure to find a node-disjoint walk from s to t in G\n", + " // This could involve using graph traversal algorithms while ensuring node-disjointness\n", + "\n", + " // Return the found walk or an empty walk if none is found\n", + " return found_walk or empty_walk\n", + "\n", + "```\n", + "\n", + "This pseudocode outlines the process of determining whether there exist node-disjoint walks for a given set of node pairs in a directed graph. The FindNodeDisjointWalk function is a placeholder for the actual algorithm to find a node-disjoint walk, which would typically involve graph traversal while ensuring node-disjointness.\n" + ], + "metadata": { + "id": "83lngFXPEGcs" + } + }, + { + "cell_type": "code", + "source": [ + "class DirectedGraph:\n", + " def __init__(self):\n", + " self.vertices = set()\n", + " self.edges = {}\n", + "\n", + " def add_edge(self, from_vertex, to_vertex):\n", + " self.vertices.add(from_vertex)\n", + " self.vertices.add(to_vertex)\n", + " self.edges.setdefault(from_vertex, []).append(to_vertex)\n", + "\n", + "def find_node_disjoint_walk(graph, source, target, visited, walk):\n", + " visited.add(source)\n", + " walk.append(source)\n", + "\n", + " if source == target:\n", + " return True\n", + "\n", + " for neighbor in graph.edges.get(source, []):\n", + " if neighbor not in visited:\n", + " if find_node_disjoint_walk(graph, neighbor, target, visited.copy(), walk):\n", + " return True\n", + "\n", + " walk.pop()\n", + " return False\n", + "\n", + "def directed_node_disjoint_walks(graph, pairs):\n", + " walks_set = set()\n", + "\n", + " for pair in pairs:\n", + " source, target = pair\n", + " walk = []\n", + " if find_node_disjoint_walk(graph, source, target, set(), walk):\n", + " walks_set.add(tuple(walk))\n", + "\n", + " return walks_set\n", + "\n", + "# Example usage\n", + "if __name__ == \"__main__\":\n", + " # Create a sample directed graph\n", + " my_graph = DirectedGraph()\n", + " my_graph.add_edge(1, 2)\n", + " my_graph.add_edge(2, 3)\n", + " my_graph.add_edge(3, 4)\n", + " my_graph.add_edge(4, 1)\n", + " my_graph.add_edge(4, 5)\n", + "\n", + " # Define pairs of nodes\n", + " node_pairs = [(1, 3), (2, 4), (5, 1)]\n", + "\n", + " # Find node-disjoint walks\n", + " disjoint_walks = directed_node_disjoint_walks(my_graph, node_pairs)\n", + "\n", + " # Print the result\n", + " print(\"Node-Disjoint Walks:\")\n", + " for walk in disjoint_walks:\n", + " print(walk)\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "P2s9BJdVERCE", + "outputId": "16aca11c-b695-498c-dc90-838669113923" + }, + "execution_count": 10, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Node-Disjoint Walks:\n", + "(1, 2, 3)\n", + "(2, 3, 4)\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 4:**\n", + "\n", + "Give a similar question like this, just the question: The Directed Disjoint Paths Problem is defined as follows. We are given a directed graph G\n", + "and k pairs of nodes (s1 , t1 ), (s2 , t2 ), . . . , (sk , tk ). The problem is to decide whether there exist node-\n", + "disjoint paths P1 , P2 , . . . , Pk so that Pi goes from si to ti .\n", + "Show that Directed Disjoint Paths is NP-complete" + ], + "metadata": { + "id": "5_gEwpoBTw29" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 4:**\n", + "\n", + "**1. In NP:**\n", + "\n", + "If someone claims that there are edge-disjoint paths connecting each pair of nodes, you can quickly check this by examining the paths to confirm that they don't share any edges. This verification can be done in polynomial time.\n", + "\n", + "**2. NP-Hardness:**\n", + "\n", + "To show that the problem is NP-hard, we perform a clever transformation from a known NP-complete problem, such as the Hamiltonian Path Problem.\n", + "\n", + "* Reduction:\n", + "\n", + "Consider a scenario where you have a graph that definitely has a Hamiltonian path (a path that visits each node exactly once). You can transform this instance into an instance of the Undirected Disjoint Paths Problem by selecting a starting node s and an ending node t. The claim is that this graph has edge-disjoint paths from s to t if and only if the original graph had a Hamiltonian path.\n", + "\n", + "* Proof:\n", + "\n", + "If the original graph had a Hamiltonian path, the edges of this path become the edge-disjoint paths from s to t. Since the Hamiltonian path visits each node exactly once, the paths from s to t don't share any edges.\n", + "\n", + "If the transformed graph has edge-disjoint paths from s to t, you can reconstruct the Hamiltonian path by connecting the paths in the order they appear. The fact that the paths are edge-disjoint ensures that the constructed path visits each node exactly once.\n", + "\n", + "* Conclusion:\n", + "\n", + "The transformation shows that if you could efficiently solve the Undirected Disjoint Paths Problem, you could also solve the Hamiltonian Path Problem. This establishes the NP-hardness of the Undirected Disjoint Paths Problem.\n", + "\n", + "By demonstrating that the problem is in NP and NP-hard, we conclude that the Undirected Disjoint Paths Problem is NP-complete." + ], + "metadata": { + "id": "2oNCUGTwWmvv" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function UndirectedDisjointPaths(G=(V, E), pairs=((s1, t1), (s2, t2), ..., (sk, tk))):\n", + " // Create an empty set to store edge-disjoint paths\n", + " paths_set = empty_set()\n", + "\n", + " // Iterate over each pair of nodes\n", + " for pair in pairs:\n", + " // Find an edge-disjoint path from si to ti\n", + " path = FindEdgeDisjointPath(G, pair)\n", + "\n", + " // If an edge-disjoint path is found, add it to the set\n", + " if path is not empty:\n", + " paths_set.add(path)\n", + "\n", + " // Check if there is at least one edge-disjoint path for each pair\n", + " return (paths_set is not empty)\n", + "\n", + "function FindEdgeDisjointPath(G=(V, E), pair=(s, t)):\n", + " // Implement a procedure to find an edge-disjoint path from s to t in G\n", + " // This could involve using graph traversal algorithms while ensuring edge-disjointness\n", + "\n", + " // Return the found path or an empty path if none is found\n", + " return found_path or empty_path\n", + "\n", + "```\n", + "This pseudocode outlines the process of determining whether there exist edge-disjoint paths for a given set of node pairs in an undirected graph. The FindEdgeDisjointPath function is a placeholder for the actual algorithm to find an edge-disjoint path, which would typically involve graph traversal while ensuring edge-disjointness.\n" + ], + "metadata": { + "id": "okNjyR7aX2D-" + } + }, + { + "cell_type": "code", + "source": [ + "class UndirectedGraph:\n", + " def __init__(self):\n", + " self.vertices = set()\n", + " self.edges = {}\n", + "\n", + " def add_edge(self, vertex1, vertex2):\n", + " self.vertices.add(vertex1)\n", + " self.vertices.add(vertex2)\n", + " self.edges.setdefault(vertex1, []).append(vertex2)\n", + " self.edges.setdefault(vertex2, []).append(vertex1)\n", + "\n", + "def find_edge_disjoint_path(graph, source, target, visited, path):\n", + " visited.add(source)\n", + " path.append(source)\n", + "\n", + " if source == target:\n", + " return True\n", + "\n", + " for neighbor in graph.edges.get(source, []):\n", + " if neighbor not in visited:\n", + " if find_edge_disjoint_path(graph, neighbor, target, visited.copy(), path):\n", + " return True\n", + "\n", + " path.pop()\n", + " return False\n", + "\n", + "def undirected_disjoint_paths(graph, pairs):\n", + " paths_set = set()\n", + "\n", + " for pair in pairs:\n", + " source, target = pair\n", + " path = []\n", + " if find_edge_disjoint_path(graph, source, target, set(), path):\n", + " paths_set.add(tuple(path))\n", + "\n", + " return paths_set\n", + "\n", + "# Example usage\n", + "if __name__ == \"__main__\":\n", + " # Create a sample undirected graph\n", + " my_graph = UndirectedGraph()\n", + " my_graph.add_edge(1, 2)\n", + " my_graph.add_edge(2, 3)\n", + " my_graph.add_edge(3, 4)\n", + " my_graph.add_edge(4, 1)\n", + " my_graph.add_edge(4, 5)\n", + "\n", + " # Define pairs of nodes\n", + " node_pairs = [(1, 3), (2, 4), (5, 1)]\n", + "\n", + " # Find edge-disjoint paths\n", + " disjoint_paths = undirected_disjoint_paths(my_graph, node_pairs)\n", + "\n", + " # Print the result\n", + " print(\"Edge-Disjoint Paths:\")\n", + " for path in disjoint_paths:\n", + " print(path)\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "qqW2kaFhYEch", + "outputId": "dcfe80ac-99fa-499a-9ba4-22c0e60b4d72" + }, + "execution_count": 11, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Edge-Disjoint Paths:\n", + "(5, 4, 3, 2, 1)\n", + "(1, 2, 3)\n", + "(2, 1, 4)\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 5:**\n", + "\n", + "Given a list of n distinct game development skills (e.g., programming, art, animation, modeling, artificial intelligence, analytics, etc.) and m potential instructors who have applied for a job, where each instructor is qualified to teach a subset of the skills, the problem is to determine, for a given number k (where k ≤ m), whether it is possible to hire at most k instructors such that they collectively cover all n skills. We'll refer to this problem as the Game Development Instructor Set Problem, aiming to find the most cost-effective set of instructors to cover all necessary skills." + ], + "metadata": { + "id": "i5nRACHQeSx-" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 5:**\n", + "\n", + "1. In NP:\n", + "\n", + "Given a proposed solution (a set of at most k instructors), we can easily verify in polynomial time whether they collectively cover all n skills. This involves checking whether the union of the skills taught by these instructors equals the set of all n skills.\n", + "\n", + "2. NP-Hardness:\n", + "\n", + "To establish NP-hardness, we need to show a polynomial-time reduction from a known NP-complete problem to the Game Development Instructor Set Problem. Let's consider reducing the Set Cover Problem to this problem.\n", + "\n", + "* Reduction:\n", + "\n", + "Given an instance I of the Set Cover Problem with a universe U and subsets S1, S2, ..., Sm, create an instance I' of the Game Development Instructor Set Problem as follows:\n", + "\n", + "The skills correspond to the elements in the universe U.\n", + "Each subset Si corresponds to an instructor who is qualified to teach the skills in Si.\n", + "\n", + "* Claim:\n", + "\n", + "I has a set cover of size k if and only if I' has a solution with at most k instructors.\n", + "\n", + "* Proof:\n", + "\n", + "If I has a set cover of size k, the corresponding instructors in I' collectively cover all the skills, and the size of the instructor set is at most k.\n", + "\n", + "If I' has a solution with at most k instructors, their qualification sets form a set cover for the skills.\n", + "\n", + "* Conclusion:\n", + "\n", + "The polynomial-time reduction from the Set Cover Problem to the Game Development Instructor Set Problem establishes NP-hardness.\n", + "\n", + "By showing both membership in NP and NP-hardness, we conclude that the Game Development Instructor Set Problem is NP-complete." + ], + "metadata": { + "id": "UpnAT7cDefAQ" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function CheapestTeacherSet(skill_list, instructor_list, k):\n", + " // Create an empty set to represent the selected instructors\n", + " selected_instructors = empty_set()\n", + "\n", + " // Iterate over each skill in the skill list\n", + " for skill in skill_list:\n", + " // Find the instructors qualified to teach the current skill\n", + " qualified_instructors = getQualifiedInstructors(skill, instructor_list)\n", + "\n", + " // Select the cheapest instructor among the qualified ones\n", + " cheapest_instructor = getCheapestInstructor(qualified_instructors)\n", + "\n", + " // Add the selected instructor to the set\n", + " selected_instructors.add(cheapest_instructor)\n", + "\n", + " // Check if the size of the selected instructor set is at most k\n", + " return (size(selected_instructors) <= k)\n", + "\n", + "function getQualifiedInstructors(skill, instructor_list):\n", + " // Create an empty set to store qualified instructors for the given skill\n", + " qualified_instructors = empty_set()\n", + "\n", + " // Iterate over each instructor in the list\n", + " for instructor in instructor_list:\n", + " // Check if the instructor is qualified to teach the current skill\n", + " if skill in instructor.skills:\n", + " qualified_instructors.add(instructor)\n", + "\n", + " return qualified_instructors\n", + "\n", + "function getCheapestInstructor(instructor_set):\n", + " // Find and return the instructor with the lowest cost from the given set\n", + " return instructor with the lowest cost in instructor_set\n", + "\n", + "```\n", + "\n", + "This pseudocode outlines the process of selecting the cheapest set of instructors to cover all required skills in the game development context. The actual implementation would require more specific details regarding the instructor and skill representations, cost considerations, and other factors based on the programming context." + ], + "metadata": { + "id": "UI2XaHqNfKv-" + } + }, + { + "cell_type": "code", + "source": [ + "class Instructor:\n", + " def __init__(self, name, skills, cost):\n", + " self.name = name\n", + " self.skills = set(skills)\n", + " self.cost = cost\n", + "\n", + "def cheapest_teacher_set(skill_list, instructor_list, k):\n", + " # Create an empty set to represent the selected instructors\n", + " selected_instructors = set()\n", + "\n", + " # Iterate over each skill in the skill list\n", + " for skill in skill_list:\n", + " # Find the instructors qualified to teach the current skill\n", + " qualified_instructors = get_qualified_instructors(skill, instructor_list)\n", + "\n", + " # Select the cheapest instructor among the qualified ones\n", + " cheapest_instructor = get_cheapest_instructor(qualified_instructors)\n", + "\n", + " # Add the selected instructor to the set\n", + " selected_instructors.add(cheapest_instructor)\n", + "\n", + " # Check if the size of the selected instructor set is at most k\n", + " return len(selected_instructors) <= k\n", + "\n", + "def get_qualified_instructors(skill, instructor_list):\n", + " # Create an empty set to store qualified instructors for the given skill\n", + " qualified_instructors = set()\n", + "\n", + " # Iterate over each instructor in the list\n", + " for instructor in instructor_list:\n", + " # Check if the instructor is qualified to teach the current skill\n", + " if skill in instructor.skills:\n", + " qualified_instructors.add(instructor)\n", + "\n", + " return qualified_instructors\n", + "\n", + "def get_cheapest_instructor(instructor_set):\n", + " # Find and return the instructor with the lowest cost from the given set\n", + " return min(instructor_set, key=lambda x: x.cost)\n", + "\n", + "# Example usage\n", + "if __name__ == \"__main__\":\n", + " # Create sample skills and instructors\n", + " skills = ['programming', 'art', 'animation', 'modeling', 'AI', 'analytics']\n", + " instructors = [\n", + " Instructor('Instructor1', ['programming', 'art'], 100),\n", + " Instructor('Instructor2', ['animation', 'modeling'], 150),\n", + " Instructor('Instructor3', ['programming', 'AI'], 120),\n", + " Instructor('Instructor4', ['analytics'], 80),\n", + " # Add more instructors as needed\n", + " ]\n", + "\n", + " # Set the maximum number of instructors to hire\n", + " max_instructors = 2\n", + "\n", + " # Check if it's possible to hire at most k instructors to cover all skills\n", + " result = cheapest_teacher_set(skills, instructors, max_instructors)\n", + "\n", + " # Print the result\n", + " if result:\n", + " print(f\"It is possible to hire at most {max_instructors} instructors to cover all skills.\")\n", + " else:\n", + " print(f\"It is NOT possible to hire at most {max_instructors} instructors to cover all skills.\")\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Y4tvJLaUfk0c", + "outputId": "88e73314-5679-4097-f0f1-3c49e2d9220a" + }, + "execution_count": 12, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "It is NOT possible to hire at most 2 instructors to cover all skills.\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 6:**\n", + "\n", + "In a software development project, you need to allocate resources to various tasks. There are n distinct tasks, each requiring a specific set of skills (programming, design, testing, etc.). You have received job applications from m potential team members. For each task, there is a subset of potential team members qualified to work on it. The question is: For a given number k (where k ≤ m), is it possible to allocate at most k team members in such a way that they collectively cover all n tasks? We'll refer to this problem as the Resource Allocation Problem. Show that the Resource Allocation Problem is NP-complete." + ], + "metadata": { + "id": "ACEBsJhHh6hF" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 6:**\n", + "\n", + "**1. In NP:**\n", + "\n", + "Given a proposed solution (a set of at most k team members), you can easily verify in polynomial time whether they collectively cover all n tasks. This involves checking whether the union of tasks assigned to these team members equals the set of all n tasks.\n", + "\n", + "**2. NP-Hardness:**\n", + "\n", + "To establish NP-hardness, we need to show a polynomial-time reduction from a known NP-complete problem to the Resource Allocation Problem. Let's consider reducing the Set Cover Problem to this problem.\n", + "\n", + "* Reduction:\n", + "\n", + "Given an instance I of the Set Cover Problem with a universe U and subsets S1, S2, ..., Sm, create an instance I' of the Resource Allocation Problem as follows:\n", + "\n", + "The tasks correspond to the elements in the universe U.\n", + "Each subset Si corresponds to a team member who is qualified to work on the tasks in Si.\n", + "\n", + "* Claim:\n", + "\n", + "I has a set cover of size k if and only if I' has a solution with at most k team members.\n", + "\n", + "* Proof:\n", + "\n", + "If I has a set cover of size k, the corresponding team members in I' collectively cover all the tasks, and the size of the team member set is at most k.\n", + "\n", + "If I' has a solution with at most k team members, their qualification sets form a set cover for the tasks.\n", + "\n", + "* Conclusion:\n", + "\n", + "The polynomial-time reduction from the Set Cover Problem to the Resource Allocation Problem establishes NP-hardness." + ], + "metadata": { + "id": "Hs0aHiL-h_H3" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function ResourceAllocation(tasks, team_members, k):\n", + " // Create an empty set to represent the selected team members\n", + " selected_team_members = empty_set()\n", + "\n", + " // Iterate over each task in the task list\n", + " for task in tasks:\n", + " // Find the team members qualified to work on the current task\n", + " qualified_team_members = getQualifiedTeamMembers(task, team_members)\n", + "\n", + " // Select at most k team members among the qualified ones\n", + " selected_team_members += selectTeamMembers(qualified_team_members, k)\n", + "\n", + " // Check if the size of the selected team member set is at most k\n", + " return (size(selected_team_members) <= k)\n", + "\n", + "function getQualifiedTeamMembers(task, team_members):\n", + " // Create an empty set to store qualified team members for the given task\n", + " qualified_team_members = empty_set()\n", + "\n", + " // Iterate over each team member in the list\n", + " for team_member in team_members:\n", + " // Check if the team member is qualified to work on the current task\n", + " if task in team_member.tasks:\n", + " qualified_team_members.add(team_member)\n", + "\n", + " return qualified_team_members\n", + "\n", + "function selectTeamMembers(qualified_team_members, k):\n", + " // Select at most k team members with some strategy (e.g., lowest cost, random, etc.)\n", + " // This depends on your specific requirements\n", + " return select k team members from qualified_team_members\n", + "\n", + "```\n", + "\n", + "This pseudocode outlines the process of selecting team members to cover various tasks in the resource allocation context. The actual implementation would require more specific details regarding the team member and task representations, the strategy for selecting team members, and other factors based on the programming context.\n" + ], + "metadata": { + "id": "G3DwPYPai7F1" + } + }, + { + "cell_type": "code", + "source": [ + "class TeamMember:\n", + " def __init__(self, name, tasks):\n", + " self.name = name\n", + " self.tasks = set(tasks)\n", + "\n", + "def resource_allocation(tasks, team_members, k):\n", + " # Create an empty set to represent the selected team members\n", + " selected_team_members = set()\n", + "\n", + " # Iterate over each task in the task list\n", + " for task in tasks:\n", + " # Find the team members qualified to work on the current task\n", + " qualified_team_members = get_qualified_team_members(task, team_members)\n", + "\n", + " # Select at most k team members among the qualified ones\n", + " selected_team_members.update(select_team_members(qualified_team_members, k))\n", + "\n", + " # Check if the size of the selected team member set is at most k\n", + " return len(selected_team_members) <= k\n", + "\n", + "def get_qualified_team_members(task, team_members):\n", + " # Create an empty set to store qualified team members for the given task\n", + " qualified_team_members = set()\n", + "\n", + " # Iterate over each team member in the list\n", + " for team_member in team_members:\n", + " # Check if the team member is qualified to work on the current task\n", + " if task in team_member.tasks:\n", + " qualified_team_members.add(team_member)\n", + "\n", + " return qualified_team_members\n", + "\n", + "def select_team_members(qualified_team_members, k):\n", + " # Select at most k team members with a simple strategy (e.g., first k members)\n", + " return list(qualified_team_members)[:k]\n", + "\n", + "# Example usage\n", + "if __name__ == \"__main__\":\n", + " # Create sample tasks and team members\n", + " tasks = ['programming', 'design', 'testing']\n", + " team_members = [\n", + " TeamMember('Member1', ['programming', 'design']),\n", + " TeamMember('Member2', ['design', 'testing']),\n", + " TeamMember('Member3', ['programming', 'testing']),\n", + " # Add more team members as needed\n", + " ]\n", + "\n", + " # Set the maximum number of team members to allocate\n", + " max_team_members = 2\n", + "\n", + " # Check if it's possible to allocate at most k team members to cover all tasks\n", + " result = resource_allocation(tasks, team_members, max_team_members)\n", + "\n", + " # Print the result\n", + " if result:\n", + " print(f\"It is possible to allocate at most {max_team_members} team members to cover all tasks.\")\n", + " else:\n", + " print(f\"It is NOT possible to allocate at most {max_team_members} team members to cover all tasks.\")\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "fopxvaZkjMPw", + "outputId": "8c7801da-e950-419d-e38d-cbbda3dbbaf6" + }, + "execution_count": 14, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "It is NOT possible to allocate at most 2 team members to cover all tasks.\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 7:**\n", + "\n", + "Imagine you are assisting in organizing a summer camp, and a problem arises. The camp intends to have at least one instructor skilled in each of the n activities covered (e.g., archery, swimming, etc.). Job applications have been received from m potential instructors. For each of the n activities, there is a subset of the m applicants qualified in that activity. The main question is: For a given number k (where k < m), is it possible to hire at most k instructors and ensure there is at least one instructor qualified in each of the n activities? We'll refer to this problem as the Multi-Activity Instructor Problem." + ], + "metadata": { + "id": "uct7Q7T5ljJX" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 7:**\n", + "\n", + "**In NP:**\n", + "\n", + "Given a proposed solution (a set of at most k instructors), it is straightforward to verify in polynomial time whether they collectively cover all n activities. This involves checking whether there is at least one instructor qualified for each activity. The verification process scales polynomially with the size of the input.\n", + "\n", + "**NP-Hardness:**\n", + "\n", + "To establish NP-hardness, we will show a polynomial-time reduction from a known NP-complete problem to the Multi-Activity Instructor Problem, using the Set Cover Problem as an example:\n", + "\n", + "**Reduction:**\n", + "\n", + "Given an instance I of the Set Cover Problem with a universe U and subsets S1, S2, ..., Sm, we create an instance I' of the Multi-Activity Instructor Problem as follows:\n", + "\n", + "The activities correspond to the elements in the universe U.\n", + "Each subset Si corresponds to an instructor who is qualified to work in the activities in Si.\n", + "\n", + "**Claim:**\n", + "\n", + "I has a set cover of size k if and only if I' has a solution with at most k instructors.\n", + "\n", + "**Proof:**\n", + "\n", + "If I has a set cover of size k, the corresponding instructors in I' collectively cover all the activities, and the size of the instructor set is at most k.\n", + "If I' has a solution with at most k instructors, their qualification sets form a set cover for the activities.\n", + "\n", + "**Conclusion:**\n", + "\n", + "The polynomial-time reduction from the Set Cover Problem to the Multi-Activity Instructor Problem establishes NP-hardness.\n", + "\n", + "\n", + "By demonstrating both membership in NP and NP-hardness, we conclude that the Multi-Activity Instructor Problem is NP-complete. This means that efficiently solving instances of this problem is at least as hard as solving any other problem in NP." + ], + "metadata": { + "id": "aIDCe6L7mYDQ" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function MultiActivityInstructor(instructors, activities, k):\n", + " selected_instructors = set() // Create an empty set to represent the selected instructors\n", + " \n", + " for activity in activities:\n", + " qualified_instructors = getQualifiedInstructors(activity, instructors)\n", + " selected_instructor = selectInstructor(qualified_instructors, k)\n", + " \n", + " if selected_instructor is None:\n", + " // No solution, return failure\n", + " return \"No solution\"\n", + " \n", + " selected_instructors.add(selected_instructor)\n", + " \n", + " // Check if the size of the selected instructor set is at most k\n", + " if size(selected_instructors) <= k:\n", + " return selected_instructors\n", + " else:\n", + " // No solution, return failure\n", + " return \"No solution\"\n", + "\n", + "function getQualifiedInstructors(activity, instructors):\n", + " qualified_instructors = set() // Create an empty set to store qualified instructors for the given activity\n", + " \n", + " for instructor in instructors:\n", + " if activity in instructor.activities:\n", + " qualified_instructors.add(instructor)\n", + " \n", + " return qualified_instructors\n", + "\n", + "function selectInstructor(qualified_instructors, k):\n", + " // Select an instructor from the qualified set using some strategy\n", + " // For example, select the one with the lowest cost or randomly select\n", + " // This depends on your specific requirements\n", + " \n", + " if size(qualified_instructors) > 0:\n", + " return an instructor from qualified_instructors\n", + " else:\n", + " return None // No qualified instructor for the given activity\n", + "\n", + "```\n", + "\n", + "This pseudocode outlines the process of selecting instructors to cover various activities in the Multi-Activity Instructor Problem. The actual implementation would require more specific details regarding the instructor and activity representations, the strategy for selecting instructors, and other factors based on the programming context." + ], + "metadata": { + "id": "zOUG4hzDonI8" + } + }, + { + "cell_type": "code", + "source": [ + "class Instructor:\n", + " def __init__(self, name, activities):\n", + " self.name = name\n", + " self.activities = set(activities)\n", + "\n", + "def multi_activity_instructor(instructors, activities, k):\n", + " selected_instructors = set()\n", + "\n", + " for activity in activities:\n", + " qualified_instructors = get_qualified_instructors(activity, instructors)\n", + " selected_instructor = select_instructor(qualified_instructors, k)\n", + "\n", + " if selected_instructor is None:\n", + " return \"No solution\"\n", + "\n", + " selected_instructors.add(selected_instructor)\n", + "\n", + " if len(selected_instructors) <= k:\n", + " return selected_instructors\n", + " else:\n", + " return \"No solution\"\n", + "\n", + "def get_qualified_instructors(activity, instructors):\n", + " qualified_instructors = set()\n", + "\n", + " for instructor in instructors:\n", + " if activity in instructor.activities:\n", + " qualified_instructors.add(instructor)\n", + "\n", + " return qualified_instructors\n", + "\n", + "def select_instructor(qualified_instructors, k):\n", + " # Select an instructor from the qualified set (e.g., random selection)\n", + " if qualified_instructors:\n", + " return next(iter(qualified_instructors))\n", + " else:\n", + " return None\n", + "\n", + "# Example usage\n", + "if __name__ == \"__main__\":\n", + " # Create sample activities and instructors\n", + " activities = ['baseball', 'volleyball', 'swimming']\n", + " instructors = [\n", + " Instructor('Instructor1', ['baseball', 'volleyball']),\n", + " Instructor('Instructor2', ['volleyball', 'swimming']),\n", + " Instructor('Instructor3', ['baseball', 'swimming']),\n", + " # Add more instructors as needed\n", + " ]\n", + "\n", + " # Set the maximum number of instructors to hire\n", + " max_instructors = 2\n", + "\n", + " # Check if it's possible to hire at most k instructors to cover all activities\n", + " result = multi_activity_instructor(instructors, activities, max_instructors)\n", + "\n", + " # Print the result\n", + " if result != \"No solution\":\n", + " print(\"Selected Instructors:\")\n", + " for instructor in result:\n", + " print(instructor.name)\n", + " else:\n", + " print(\"No solution\")\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "-4apS4Pwovvh", + "outputId": "25ea7983-6549-453a-e1d3-75a85fd8cdde" + }, + "execution_count": 15, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Selected Instructors:\n", + "Instructor2\n", + "Instructor3\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 8:**\n", + "\n", + "Suppose you're involved in organizing a multidisciplinary project, and a new challenge arises. The project requires a diverse team, ensuring at least one team member is skilled in each of the n disciplines (e.g., programming, design, marketing, etc.). Job applications have been received from m potential team members. For each of the n disciplines, there is a subset of the m applicants qualified in that discipline. The primary question is: For a given number k (where k < m), is it possible to form a project team with at most k members and ensure there is at least one team member qualified in each of the n disciplines? We'll call this the Multidisciplinary Project Team Formation Problem. Show that the Multidisciplinary Project Team Formation Problem is NP-complete." + ], + "metadata": { + "id": "NtyllUj9qEcw" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 8:**\n", + "\n", + "**1. Verification (In NP):**\n", + "\n", + "Given a potential solution (a team with at most k members), it's straightforward to check in a reasonable amount of time whether they cover all n disciplines. The key is ensuring there's at least one team member proficient in each discipline.\n", + "\n", + "**2. Difficulty (NP-Hardness):**\n", + "\n", + "To prove the problem's complexity, we'll demonstrate a polynomial-time conversion from a known NP-complete problem, like the Efficient Recruiting Problem, to our Multidisciplinary Project Team Formation Problem.\n", + "\n", + "**Conversion Process:**\n", + "\n", + "Take an instance I of the Efficient Recruiting Problem and create a corresponding instance I' of the Multidisciplinary Project Team Formation Problem.\n", + "Link the disciplines and team members in I to those in I'.\n", + "Match the qualification subsets in I to those in I'.\n", + "\n", + "**Key Claim:**\n", + "\n", + "If there's a solution to I with at most k team members, then the corresponding team members in I' form a multidisciplinary project team covering all disciplines.\n", + "Conversely, if there's a solution to I' with at most k team members and at least one member per discipline, it is also a valid solution for I.\n", + "\n", + "**Conclusion:**\n", + "\n", + "The transformation from the Efficient Recruiting Problem to the Multidisciplinary Project Team Formation Problem proves that the latter is NP-hard.\n", + "\n", + "By demonstrating both NP membership (verifiability in polynomial time) and NP-hardness (through reduction from a known NP-complete problem), we establish that the Multidisciplinary Project Team Formation Problem is NP-complete. Solving instances of this problem is at least as challenging as solving any other problem in NP." + ], + "metadata": { + "id": "9_jpCMMRqIml" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function formProjectTeam(qualifiedMembers, disciplines, maxTeamSize):\n", + " selectedTeamMembers = set() // Create an empty set to represent the selected team members\n", + " \n", + " for discipline in disciplines:\n", + " qualifiedMembersForDiscipline = getQualifiedMembersForDiscipline(discipline, qualifiedMembers)\n", + " selectedMember = selectTeamMember(qualifiedMembersForDiscipline, maxTeamSize)\n", + " \n", + " if selectedMember is None:\n", + " // No solution, return failure\n", + " return \"No solution\"\n", + " \n", + " selectedTeamMembers.add(selectedMember)\n", + " \n", + " // Check if the size of the selected team member set is at most maxTeamSize\n", + " if size(selectedTeamMembers) <= maxTeamSize:\n", + " return selectedTeamMembers\n", + " else:\n", + " // No solution, return failure\n", + " return \"No solution\"\n", + "\n", + "function getQualifiedMembersForDiscipline(discipline, qualifiedMembers):\n", + " qualifiedMembersForDiscipline = set() // Create an empty set to store qualified members for the given discipline\n", + " \n", + " for member in qualifiedMembers:\n", + " if discipline in member.disciplines:\n", + " qualifiedMembersForDiscipline.add(member)\n", + " \n", + " return qualifiedMembersForDiscipline\n", + "\n", + "function selectTeamMember(qualifiedMembersForDiscipline, maxTeamSize):\n", + " // Select a team member from the qualified set (e.g., random selection)\n", + " if size(qualifiedMembersForDiscipline) > 0:\n", + " return a member from qualifiedMembersForDiscipline\n", + " else:\n", + " return None // No qualified member for the given discipline\n", + "\n", + "```\n", + "\n", + "This pseudocode outlines the process of selecting team members to cover various disciplines in the Multidisciplinary Project Team Formation Problem. The actual implementation would require more specific details regarding the member and discipline representations, the strategy for selecting members, and other factors based on the programming context.\n" + ], + "metadata": { + "id": "1CpqPO6uqtaD" + } + }, + { + "cell_type": "code", + "source": [ + "import random\n", + "\n", + "class TeamMember:\n", + " def __init__(self, name, disciplines):\n", + " self.name = name\n", + " self.disciplines = set(disciplines)\n", + "\n", + "def form_project_team(qualified_members, disciplines, max_team_size):\n", + " selected_team_members = set()\n", + "\n", + " for discipline in disciplines:\n", + " qualified_members_for_discipline = get_qualified_members_for_discipline(discipline, qualified_members)\n", + " selected_member = select_team_member(qualified_members_for_discipline, max_team_size)\n", + "\n", + " if selected_member is None:\n", + " return \"No solution\"\n", + "\n", + " selected_team_members.add(selected_member)\n", + "\n", + " if len(selected_team_members) <= max_team_size:\n", + " return selected_team_members\n", + " else:\n", + " return \"No solution\"\n", + "\n", + "def get_qualified_members_for_discipline(discipline, qualified_members):\n", + " qualified_members_for_discipline = set()\n", + "\n", + " for member in qualified_members:\n", + " if discipline in member.disciplines:\n", + " qualified_members_for_discipline.add(member)\n", + "\n", + " return qualified_members_for_discipline\n", + "\n", + "def select_team_member(qualified_members_for_discipline, max_team_size):\n", + " # Select a team member from the qualified set (e.g., random selection)\n", + " if qualified_members_for_discipline:\n", + " return random.choice(list(qualified_members_for_discipline))\n", + " else:\n", + " return None\n", + "\n", + "# Example usage\n", + "if __name__ == \"__main__\":\n", + " # Create sample disciplines and team members\n", + " disciplines = ['programming', 'design', 'marketing']\n", + " team_members = [\n", + " TeamMember('Member1', ['programming', 'design']),\n", + " TeamMember('Member2', ['design', 'marketing']),\n", + " TeamMember('Member3', ['programming', 'marketing']),\n", + " # Add more team members as needed\n", + " ]\n", + "\n", + " # Set the maximum number of team members to select\n", + " max_team_size = 2\n", + "\n", + " # Check if it's possible to form a project team with at most k members\n", + " result = form_project_team(team_members, disciplines, max_team_size)\n", + "\n", + " # Print the result\n", + " if result != \"No solution\":\n", + " print(\"Selected Team Members:\")\n", + " for member in result:\n", + " print(member.name)\n", + " else:\n", + " print(\"No solution\")\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gKod0zltq2xi", + "outputId": "e9d85b25-9e21-4731-ba24-c8b7baf38d21" + }, + "execution_count": 16, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Selected Team Members:\n", + "Member3\n", + "Member1\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 9:**\n", + "\n", + "Suppose you are part of a community living arrangement, the Harmony Homestead, with n − 1 other individuals. In the upcoming n days, each person is assigned the responsibility to complete a task exactly once, ensuring that someone is designated for each task daily.\n", + "\n", + "Certainly, everyone has conflicting commitments on certain days (like exams, concerts, etc.), making the task of assigning individuals to tasks challenging. For specificity, label the individuals as P ∈ {p1, . . . , pn}, the tasks as T ∈ {t1, . . . , tn}, and for each person p_i, there exists a set of incompatible tasks S_i ⊂ {t1, . . . , tn}. Leaving S_i empty is not allowed.\n", + "\n", + "Moreover, if a person is not scheduled for any of the n days, they are obligated to pay $200 to hire someone to perform the task on their behalf.\n", + "\n", + "A. Formulate this problem as a maximum flow problem, aiming to maximize the number of successful task assignments between individuals and tasks.\n", + "\n", + "B. Investigate whether it is always feasible to assign each of the n people to one of the n tasks. Provide a proof either establishing that it is always possible or demonstrating scenarios where achieving complete assignment is impossible." + ], + "metadata": { + "id": "g9r_CXZfq-lj" + } + }, + { + "cell_type": "markdown", + "source": [ + "### **Solution 9:**\n", + "\n", + "**A. Express this problem as a maximum flow problem that schedules the maximum number of matches between the people and the nights.**\n", + "\n", + "To express the cooperative apartment scheduling problem as a maximum flow problem, we need to define nodes and capacities. Consider the following:\n", + "\n", + "1. Nodes:\n", + "\n", + "* Create a source node 'Source' and a sink node 'Sink.'\n", + "* Create nodes for each person (p1, p2, ..., pn) and each night (n1, n2, ..., nn).\n", + "\n", + "2. Edges and Capacities:\n", + "\n", + "* Connect the source node to each person node with an edge having a capacity of 1. This represents the requirement that each person should be scheduled exactly once.\n", + "* Connect each night node to the sink node with an edge having a capacity of 1. This represents the requirement that each night should have exactly one cook.\n", + "* Connect each person node to the night nodes for the nights they are available. The capacity for these edges is also 1.\n", + "\n", + "3. Maximize Flow:\n", + "\n", + "* By finding the maximum flow in this network, we schedule the maximum number of matches between people and nights, ensuring that each person is assigned to one night, and each night has one cook.\n", + "\n", + "**B. Can all n people always be matched with one of the n nights? Prove that it can or cannot.**\n", + "\n", + "The feasibility of matching all n people with one of the n nights depends on whether the constructed flow network has a perfect matching (a flow of value n).\n", + "\n", + "1. Proof of Feasibility:\n", + "\n", + "* A perfect matching exists if and only if the maximum flow in the network is n.\n", + "* The Ford-Fulkerson algorithm or any other maximum flow algorithm can be employed to determine the maximum flow.\n", + "* If the maximum flow equals n, a perfect matching exists, and everyone can be matched with one of the nights.\n", + "* If the maximum flow is less than n, it indicates that there are individuals who cannot be matched, and they would need to pay $200 to hire a cook.\n", + "\n", + "2. Proof of Impossibility:\n", + "\n", + "* The impossibility would be proven by demonstrating a scenario where the maximum flow is less than n, meaning that a perfect matching cannot be achieved.\n", + "* This could occur if there are constraints or conflicts that prevent the scheduling of all individuals.\n", + "\n", + "In conclusion, the cooperative apartment scheduling problem can be efficiently solved using a maximum flow algorithm, and the feasibility of matching all individuals with one of the nights depends on the existence of a perfect matching in the constructed flow network" + ], + "metadata": { + "id": "f8CZTEvWwMTR" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function fordFulkerson(graph, source, sink):\n", + " residualGraph = createResidualGraph(graph)\n", + " maxFlow = 0\n", + " \n", + " while there is an augmenting path from source to sink in residualGraph:\n", + " augmentingFlow = findAugmentingFlow(residualGraph, source, sink)\n", + " maxFlow += augmentingFlow\n", + " updateResidualGraph(residualGraph, augmentingFlow)\n", + "\n", + " return maxFlow\n", + "\n", + "function createResidualGraph(graph):\n", + " // Create a residual graph based on the original graph\n", + " // Initialize residual capacities as the capacities of the original edges\n", + "\n", + "function findAugmentingFlow(residualGraph, source, sink):\n", + " // Implement a method to find the augmenting path and calculate the bottleneck capacity\n", + "\n", + "function updateResidualGraph(residualGraph, augmentingFlow):\n", + " // Update the residual capacities in the graph based on the augmenting flow\n", + "\n", + "function main():\n", + " // Create a graph representation of the cooperative apartment scheduling problem\n", + " // Nodes include source, sink, people, and nights\n", + " // Edges include connections between source and people, people and nights, and nights and sink\n", + "\n", + " // Run the Ford-Fulkerson algorithm to find the maximum flow\n", + " maxFlow = fordFulkerson(graph, source, sink)\n", + "\n", + " // Check if all people can be matched with one of the nights\n", + " if maxFlow == number_of_people:\n", + " print(\"Feasible: All people can be matched with one of the nights.\")\n", + " else:\n", + " print(\"Infeasible: Some people cannot be matched with a night and must pay $200.\")\n", + "\n", + "```\n", + "\n" + ], + "metadata": { + "id": "CzyfVUJnxjDY" + } + }, + { + "cell_type": "code", + "source": [ + "def cooperative_apartment_scheduling(people, nights, constraints):\n", + " # Create a graph represented as an adjacency matrix\n", + " graph = create_cooperative_graph(people, nights, constraints)\n", + "\n", + " source = 0\n", + " sink = len(people) + len(nights) + 1\n", + "\n", + " # Initialize residual capacities as the capacities of the original edges\n", + " residual_capacities = [[graph[i][j] for j in range(sink + 1)] for i in range(sink + 1)]\n", + "\n", + " max_flow = 0\n", + "\n", + " while True:\n", + " # Find an augmenting path using DFS\n", + " path = find_augmenting_path(residual_capacities, source, sink)\n", + "\n", + " if not path:\n", + " break\n", + "\n", + " # Find the bottleneck capacity along the augmenting path\n", + " bottleneck_capacity = min(residual_capacities[path[i]][path[i + 1]] for i in range(len(path) - 1))\n", + "\n", + " # Update residual capacities\n", + " for i in range(len(path) - 1):\n", + " residual_capacities[path[i]][path[i + 1]] -= bottleneck_capacity\n", + " residual_capacities[path[i + 1]][path[i]] += bottleneck_capacity\n", + "\n", + " max_flow += bottleneck_capacity\n", + "\n", + " return max_flow\n", + "\n", + "def create_cooperative_graph(people, nights, constraints):\n", + " num_people = len(people)\n", + " num_nights = len(nights)\n", + "\n", + " graph = [[0] * (num_people + num_nights + 2) for _ in range(num_people + num_nights + 2)]\n", + "\n", + " # Connect source to people\n", + " for i in range(1, num_people + 1):\n", + " graph[0][i] = 1\n", + "\n", + " # Connect nights to sink\n", + " for i in range(num_people + 1, num_people + num_nights + 1):\n", + " graph[i][num_people + num_nights + 1] = 1\n", + "\n", + " # Connect people to nights based on constraints\n", + " for i in range(1, num_people + 1):\n", + " for j in range(num_people + 1, num_people + num_nights + 1):\n", + " if nights[j - num_people - 1] not in constraints[people[i - 1]]:\n", + " graph[i][j] = 1\n", + "\n", + " return graph\n", + "\n", + "def find_augmenting_path(graph, source, sink):\n", + " visited = set()\n", + " stack = [(source, [source])]\n", + "\n", + " while stack:\n", + " (vertex, path) = stack.pop()\n", + "\n", + " if vertex not in visited:\n", + " visited.add(vertex)\n", + "\n", + " for next_vertex, capacity in enumerate(graph[vertex]):\n", + " if capacity > 0 and next_vertex not in visited:\n", + " if next_vertex == sink:\n", + " return path + [next_vertex]\n", + " else:\n", + " stack.append((next_vertex, path + [next_vertex]))\n", + "\n", + " return None\n", + "\n", + "def main():\n", + " # Define people, nights, and constraints\n", + " people = ['p1', 'p2', 'p3']\n", + " nights = ['n1', 'n2', 'n3']\n", + " constraints = {'p1': ['n2'], 'p2': ['n1'], 'p3': ['n3']}\n", + "\n", + " # Find the maximum flow using the custom implementation\n", + " max_flow = cooperative_apartment_scheduling(people, nights, constraints)\n", + "\n", + " # Check if all people can be matched with one of the nights\n", + " if max_flow == len(people):\n", + " print(\"Feasible: All people can be matched with one of the nights.\")\n", + " else:\n", + " print(\"Infeasible: Some people cannot be matched with a night and must pay $200.\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " main()\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "L5OcSv8VyOUJ", + "outputId": "207d89f9-8754-4cf5-a095-9c61d84b67d5" + }, + "execution_count": 18, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Feasible: All people can be matched with one of the nights.\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### **Problem 10:**\n", + "\n", + "Consider a scenario where you are part of a shared living community, the Unity Residence, with n − 1 other individuals. Over the next n days, each person is entrusted with a specific task, ensuring that each task is assigned to someone daily.\n", + "\n", + "Certainly, scheduling conflicts arise as everyone has commitments on particular days, such as exams or concerts, complicating the task of assigning individuals to tasks. Specifically, individuals are denoted as P ∈ {p1, . . . , pn}, tasks as T ∈ {t1, . . . , tn}, and for each person p_i, there exists a set of incompatible tasks S_i ⊂ {t1, . . . , tn}. Leaving S_i empty is not permitted.\n", + "\n", + "Furthermore, if a person is not assigned a task for any of the n days, they must pay $200 to hire someone for task completion.\n", + "\n", + "A. Express this problem as a maximum flow problem, striving to optimize the number of successful task assignments between individuals and tasks.\n", + "\n", + "B. Investigate whether it is consistently feasible to assign each of the n people to one of the n tasks. Present a proof establishing its universal possibility or illustrate scenarios where achieving complete assignment is impractical." + ], + "metadata": { + "id": "R3eM2WPQyYF5" + } + }, + { + "cell_type": "markdown", + "source": [ + "**A. Maximum Flow Formulation:**\n", + "\n", + "To formulate this as a maximum flow problem, we can represent it as a flow network. Let's define:\n", + "\n", + "* Nodes: Source, sink, individuals (p1, ..., pn), tasks (t1, ..., tn).\n", + "* Edges:\n", + "* Connect the source to each individual with a capacity of 1, representing the requirement for each person to be assigned a task.\n", + "* Connect each task to the sink with a capacity of 1, representing the need for each task to be assigned to someone.\n", + "* Connect individuals to tasks based on compatibility, with capacities of 1 where compatible (not in Si) and 0 where incompatible.\n", + "\n", + "The objective is to maximize the flow in this network, representing the maximum number of successful task assignments.\n", + "\n", + "**B. Feasibility Investigation:**\n", + "\n", + "Let's analyze the feasibility of assigning each person to one of the tasks:\n", + "\n", + "**Proof of Feasibility:**\n", + "\n", + "* If there exists a perfect matching (i.e., a matching where each person is assigned to a task and each task is assigned to a person) in the bipartite graph representing compatibility, then it is feasible.\n", + "* Algorithms like the Hopcroft-Karp algorithm can efficiently find such a perfect matching.\n", + "\n", + "**Proof of Impossibility:**\n", + "\n", + "* If there is no perfect matching in the bipartite graph, then there are scenarios where achieving a complete assignment is impossible.\n", + "* This could occur when the number of individuals is greater than the number of tasks or when certain compatibility constraints prevent a perfect matching.\n" + ], + "metadata": { + "id": "c6XJ79y81MmF" + } + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "\n", + "```\n", + "function fordFulkerson(graph, source, sink):\n", + " residualGraph = createResidualGraph(graph)\n", + " maxFlow = 0\n", + " \n", + " while there is an augmenting path from source to sink in residualGraph:\n", + " augmentingFlow = findAugmentingFlow(residualGraph, source, sink)\n", + " maxFlow += augmentingFlow\n", + " updateResidualGraph(residualGraph, augmentingFlow)\n", + "\n", + " return maxFlow\n", + "\n", + "function createResidualGraph(graph):\n", + " // Create a residual graph based on the original graph\n", + " // Initialize residual capacities as the capacities of the original edges\n", + "\n", + "function findAugmentingFlow(residualGraph, source, sink):\n", + " // Implement a method to find the augmenting path and calculate the bottleneck capacity\n", + "\n", + "function updateResidualGraph(residualGraph, augmentingFlow):\n", + " // Update the residual capacities in the graph based on the augmenting flow\n", + "\n", + "function main():\n", + " // Create a graph representation of the task assignment problem\n", + " // Nodes include source, sink, individuals, and tasks\n", + " // Edges include connections between source and individuals, individuals and tasks, and tasks and sink\n", + "\n", + " // Run the Ford-Fulkerson algorithm to find the maximum flow\n", + " maxFlow = fordFulkerson(graph, source, sink)\n", + "\n", + " // Check if a perfect matching exists, indicating feasibility\n", + " if maxFlow == number_of_people:\n", + " print(\"Feasible: There is a perfect assignment for each person.\")\n", + " else:\n", + " print(\"Infeasible: Some people cannot be assigned to a task, and must pay $200.\")\n", + "\n", + "```\n", + "This pseudocode outlines the basic structure of the Ford-Fulkerson algorithm and its application to the task assignment problem. The actual implementation will depend on your specific programming language and data structures.\n" + ], + "metadata": { + "id": "CGqBHJHJ2Of-" + } + }, + { + "cell_type": "code", + "source": [ + "class Graph:\n", + " def __init__(self, vertices):\n", + " self.V = vertices\n", + " self.graph = [[0] * vertices for _ in range(vertices)]\n", + "\n", + " def add_edge(self, u, v, capacity):\n", + " self.graph[u][v] = capacity\n", + "\n", + "def ford_fulkerson(graph, source, sink):\n", + " def bfs(residual, parent):\n", + " visited = [False] * graph.V\n", + " queue = [source]\n", + " visited[source] = True\n", + "\n", + " while queue:\n", + " u = queue.pop(0)\n", + "\n", + " for v, capacity in enumerate(residual[u]):\n", + " if not visited[v] and capacity > 0:\n", + " queue.append(v)\n", + " parent[v] = u\n", + " visited[v] = True\n", + "\n", + " return visited[sink]\n", + "\n", + " def update_residual(residual, path, bottleneck):\n", + " for u, v in zip(path, path[1:]):\n", + " residual[u][v] -= bottleneck\n", + " residual[v][u] += bottleneck\n", + "\n", + " residual_graph = [row[:] for row in graph.graph]\n", + " parent = [-1] * graph.V\n", + " max_flow = 0\n", + "\n", + " while bfs(residual_graph, parent):\n", + " path = []\n", + " v = sink\n", + "\n", + " while v != -1:\n", + " path.insert(0, v)\n", + " v = parent[v]\n", + "\n", + " bottleneck = min(residual_graph[u][v] for u, v in zip(path, path[1:]))\n", + "\n", + " max_flow += bottleneck\n", + " update_residual(residual_graph, path, bottleneck)\n", + "\n", + " return max_flow\n", + "\n", + "def main():\n", + " num_people = 3\n", + " num_tasks = 3\n", + " source = 0\n", + " sink = num_people + num_tasks + 1\n", + "\n", + " g = Graph(num_people + num_tasks + 2)\n", + "\n", + " # Add edges between source and people\n", + " for i in range(1, num_people + 1):\n", + " g.add_edge(source, i, 1)\n", + "\n", + " # Add edges between tasks and sink\n", + " for i in range(num_people + 1, sink):\n", + " g.add_edge(i, sink, 1)\n", + "\n", + " # Add edges between people and tasks based on compatibility\n", + " g.add_edge(1, 4, 1)\n", + " g.add_edge(2, 5, 1)\n", + " g.add_edge(3, 6, 1)\n", + "\n", + " max_flow = ford_fulkerson(g, source, sink)\n", + "\n", + " if max_flow == num_people:\n", + " print(\"Feasible: There is a perfect assignment for each person.\")\n", + " else:\n", + " print(\"Infeasible: Some people cannot be assigned to a task, and must pay $200.\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " main()\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3JNI-Dlk216U", + "outputId": "8c1fdbe9-dba2-4acf-ed8e-e16204df03bc" + }, + "execution_count": 19, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Feasible: There is a perfect assignment for each person.\n" + ] + } + ] + } + ] +} \ No newline at end of file