0% found this document useful (0 votes)
71 views

Week 7-11 Solutions

The document contains solutions to three questions related to graph algorithms: 1. It implements Dijkstra's algorithm to find the shortest paths from a source vertex to all other vertices in a graph represented using an adjacency matrix. 2. It implements the Bellman-Ford algorithm to find the shortest paths from a source vertex to all other vertices in a graph, including graphs with negative edge weights. It checks for negative cycles. 3. It uses dynamic programming to find the shortest path between two vertices with exactly k edges, by building a 3D DP table to store the shortest paths between all pairs of vertices using k edges.

Uploaded by

himanshu agarwal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
71 views

Week 7-11 Solutions

The document contains solutions to three questions related to graph algorithms: 1. It implements Dijkstra's algorithm to find the shortest paths from a source vertex to all other vertices in a graph represented using an adjacency matrix. 2. It implements the Bellman-Ford algorithm to find the shortest paths from a source vertex to all other vertices in a graph, including graphs with negative edge weights. It checks for negative cycles. 3. It uses dynamic programming to find the shortest path between two vertices with exactly k edges, by building a 3D DP table to store the shortest paths between all pairs of vertices using k edges.

Uploaded by

himanshu agarwal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 19

WEEK 7 solution

Q1:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define V 6 // Number of vertices in the graph

// Function to find the vertex with minimum distance value, from the set of vertices not yet
included in shortest path tree
int minDistance(int dist[], int sptSet[]) {
int min = INT_MAX, min_index;

for (int v = 0; v < V; v++)


if (sptSet[v] == 0 && dist[v] <= min)
min = dist[v], min_index = v;

return min_index;
}

// Function to print the final shortest path from source vertex to all other vertices
void printSolution(int dist[]) {
printf("Vertex \t\t Distance from Source\n");
for (int i = 0; i < V; i++)
printf("%d \t\t %d\n", i, dist[i]);
}

// Function that implements Dijkstra's single source shortest path algorithm for a graph
represented using adjacency matrix representation
void dijkstra(int graph[V][V], int src) {
int dist[V]; // The output array. dist[i] will hold the shortest distance from src to i

int sptSet[V]; // sptSet[i] will be 1 if vertex i is included in shortest path tree or shortest distance
from src to i is finalized

// Initialize all distances as INFINITE and sptSet[] as 0


for (int i = 0; i < V; i++)
dist[i] = INT_MAX, sptSet[i] = 0;

// Distance of source vertex from itself is always 0


dist[src] = 0;

// Find shortest path for all vertices


for (int count = 0; count < V - 1; count++) {
// Pick the minimum distance vertex from the set of vertices not yet processed.
int u = minDistance(dist, sptSet);

// Mark the picked vertex as processed


sptSet[u] = 1;
// Update dist value of the adjacent vertices of the picked vertex.
for (int v = 0; v < V; v++)
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
}

// print the constructed distance array


printSolution(dist);
}

int main() {
int graph[V][V] = { {0, 7, 9, 0, 0, 14},
{7, 0, 10, 15, 0, 0},
{9, 10, 0, 11, 0, 2},
{0, 15, 11, 0, 6, 0},
{0, 0, 0, 6, 0, 9},
{14, 0, 2, 0, 9, 0} };

dijkstra(graph, 0); // Run Dijkstra's Algorithm with source vertex 0

return 0;
}

Q2:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define V 6 // Number of vertices in the graph

// Function to print the final shortest path from source vertex to all other vertices
void printSolution(int dist[]) {
printf("Vertex \t\t Distance from Source\n");
for (int i = 0; i < V; i++)
printf("%d \t\t %d\n", i, dist[i]);
}

// Function that implements Bellman-Ford algorithm for a graph represented using adjacency
matrix representation
void bellmanFord(int graph[V][V], int src) {
int dist[V]; // The output array. dist[i] will hold the shortest distance from src to i

// Initialize distances from src to all other vertices as INFINITE


for (int i = 0; i < V; i++)
dist[i] = INT_MAX;
dist[src] = 0;

// Relax all edges V-1 times. A simple shortest path from src to any other vertex can have at
most V-1 edges
for (int i = 0; i < V - 1; i++) {
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
}
}
}

// Check for negative-weight cycles. If we get a shorter path, then there is a cycle
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) {
printf("Graph contains negative-weight cycle\n");
return;
}
}
}

// print the constructed distance array


printSolution(dist);
}

int main() {

int graph[V][V] = { {0, 6, 0, 0, 7, 0},


{0, 0, 5, -4, 8, -2},
{0, -2, 0, 0, 0, 0},
{2, 0, 7, 0, 0, 0},
{0, -3, 9, 2, 0, 0},
{0, 0, 0, 0, -5, 0} };

bellmanFord(graph, 0); // Run Bellman-Ford Algorithm with source vertex 0

return 0;
}

Q3:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define V 4 // Number of vertices in the graph

// Function to find the shortest path with exactly k edges from source to destination
int shortestPathWithKEdges(int graph[V][V], int src, int dest, int k) {
int dp[V][V][k+1]; // 3D array to store the shortest distance between each pair of vertices with k
edges on the path

// Initialize the 3D array with infinity for all pairs of vertices with k edges
for (int e = 0; e <= k; e++) {
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
dp[i][j][e] = INT_MAX;
}
}
}

// Initialize the 3D array with the weight of the direct edges between the vertices
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
if (graph[i][j] != 0) {
dp[i][j][1] = graph[i][j];
}
}
}

// Compute the shortest distance between each pair of vertices with exactly k edges on the
path
for (int e = 2; e <= k; e++) {
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
for (int x = 0; x < V; x++) {
if (graph[i][x] != 0 && dp[x][j][e-1] != INT_MAX) {
dp[i][j][e] = (dp[i][j][e] < dp[i][x][1] + dp[x][j][e-1]) ? dp[i][j][e] : (dp[i][x][1] + dp[x][j]
[e-1]);
}
}
}
}
}

// Return the shortest distance between the source and destination vertices with exactly k
edges on the path
return dp[src][dest][k];
}

int main() {
// The example graph from the assignment
int graph[V][V] = { {0, 10, 3, 0},
{0, 0, 0, 7},
{0, 0, 0, 6},
{0, 0, 0, 0} };

int src = 0; // Source vertex


int dest = 3; // Destination vertex
int k = 2; // Number of edges on the path

int shortestPath = shortestPathWithKEdges(graph, src, dest, k); // Find the weight of the
shortest path

if (shortestPath == INT_MAX) {
printf("There is no path with exactly %d edges from vertex %d to vertex %d\n", k, src, dest);
} else {
printf("The weight of the shortest path with exactly %d edges from vertex %d to vertex %d is
%d\n", k, src, dest, shortestPath);
}

return 0;
}

WEEK 8: Solution

Q1:

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

#define MAX_CITIES 100

int minimum_cost(int graph[MAX_CITIES][MAX_CITIES], int num_cities) {


bool visited[MAX_CITIES] = { false };
int MST[MAX_CITIES];
int start_city = 0; // Starting city (can be any)
int min_cost = 0;

visited[start_city] = true;
int edges[MAX_CITIES];
int num_edges = 0;

for (int i = 0; i < num_cities; i++) {


if (graph[start_city][i] != 0) {
edges[num_edges++] = i;
}
}

while (num_edges > 0) {


int min_cost_edge = INT_MAX;
int min_cost_dest = -1;

for (int i = 0; i < num_edges; i++) {


int dest = edges[i];

if (!visited[dest] && graph[start_city][dest] < min_cost_edge) {


min_cost_edge = graph[start_city][dest];
min_cost_dest = dest;
}
}
if (min_cost_dest != -1) {
visited[min_cost_dest] = true;
MST[min_cost_dest] = min_cost_edge;
min_cost += min_cost_edge;

for (int i = 0; i < num_cities; i++) {


if (graph[min_cost_dest][i] != 0 && !visited[i]) {
edges[num_edges++] = i;
}
}
}

num_edges--;
edges[min_cost_dest] = edges[num_edges];
}

return min_cost;
}

int main() {
int num_cities;
printf("Enter the number of cities: ");
scanf("%d", &num_cities);

int graph[MAX_CITIES][MAX_CITIES];
printf("Enter the adjacency matrix:\n");

for (int i = 0; i < num_cities; i++) {


for (int j = 0; j < num_cities; j++) {
scanf("%d", &graph[i][j]);
}
}

int min_cost = minimum_cost(graph, num_cities);


printf("Minimum cost: %d\n", min_cost);

return 0;
}

Q2:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define MAX_CITIES 100

// Structure to represent an edge


struct Edge {
int src, dest, weight;
};
// Structure to represent a subset for union-find
struct Subset {
int parent;
int rank;
};

// Compare function used by qsort to sort edges based on weight


int compare(const void* a, const void* b) {
struct Edge* edge1 = (struct Edge*)a;
struct Edge* edge2 = (struct Edge*)b;
return edge1->weight - edge2->weight;
}

// Find the parent of a subset (with path compression)


int find(struct Subset subsets[], int i) {
if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}

// Union of two subsets (with union by rank)


void unionSets(struct Subset subsets[], int x, int

Q3:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define MAX_VERTICES 100

struct Edge {
int src, dest, weight;
};

struct Subset {
int parent;
int rank;
};

int compare(const void* a, const void* b) {


struct Edge* edge1 = (struct Edge*)a;
struct Edge* edge2 = (struct Edge*)b;
return edge2->weight - edge1->weight;
}

int find(struct Subset subsets[], int i) {


if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}

void unionSets(struct Subset subsets[], int x, int y) {


int xroot = find(subsets, x);
int yroot = find(subsets, y);

if (subsets[xroot].rank < subsets[yroot].rank)


subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
else {
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}

int maximum_budget(struct Edge edges[], int num_edges, int num_vertices) {


struct Subset* subsets = (struct Subset*)malloc(num_vertices * sizeof(struct Subset));
int max_spanning_weight = 0;

for (int i = 0; i < num_vertices; i++) {


subsets[i].parent = i;
subsets[i].rank = 0;
}

qsort(edges, num_edges, sizeof(struct Edge), compare);

for (int i = 0; i < num_edges; i++) {


int src = edges[i].src;
int dest = edges[i].dest;
int src_parent = find(subsets, src);
int dest_parent = find(subsets, dest);

if (src_parent != dest_parent) {
max_spanning_weight += edges[i].weight;
unionSets(subsets, src_parent, dest_parent);
}
}

free(subsets);

return max_spanning_weight;
}

int main() {
int num_vertices;
printf("Enter the number of vertices in the graph: ");
scanf("%d", &num_vertices);

int num_edges = num_vertices * (num_vertices - 1) / 2;


struct Edge edges[num_edges];

int edge_index = 0;

printf("Enter the adjacency matrix:\n");


for (int i = 0; i < num_vertices; i++) {
for (int j = 0; j < i; j++) {
int weight;
scanf("%

WEEK 9: Solutions

Q1:

#include <stdio.h>
#include <stdbool.h>

#define INF 9999


#define MAX_VERTICES 100

void floyd_warshall(int graph[MAX_VERTICES][MAX_VERTICES], int num_vertices) {


int dist[MAX_VERTICES][MAX_VERTICES];

// Initialize the distance matrix with the graph


for (int i = 0; i < num_vertices; i++) {
for (int j = 0; j < num_vertices; j++) {
dist[i][j] = graph[i][j];
}
}

// Perform the Floyd-Warshall algorithm


for (int k = 0; k < num_vertices; k++) {
for (int i = 0; i < num_vertices; i++) {
for (int j = 0; j < num_vertices; j++) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}

// Print the shortest distance matrix


printf("Shortest Distance Matrix:\n");
for (int i = 0; i < num_vertices; i++) {
for (int j = 0; j < num_vertices; j++) {
if (dist[i][j] == INF) {
printf("INF ");
} else {
printf("%d ", dist[i][j]);
}
}
printf("\n");
}
}

int main() {
int num_vertices;
printf("Enter the number of vertices in the graph: ");
scanf("%d", &num_vertices);

int graph[MAX_VERTICES][MAX_VERTICES];
printf("Enter the adjacency matrix:\n");

for (int i = 0; i < num_vertices; i++) {


for (int j = 0; j < num_vertices; j++) {
scanf("%d", &graph[i][j]);
}
}

floyd_warshall(graph, num_vertices);

return 0;
}

Q2:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

struct Item {
int weight;
int value;
double ratio;
};

int compare(const void* a, const void* b) {


struct Item* item1 = (struct Item*)a;
struct Item* item2 = (struct Item*)b;
return (item2->ratio - item1->ratio) > 0 ? 1 : -1;
}

void fractional_knapsack(struct Item items[], int num_items, int capacity) {


qsort(items, num_items, sizeof(struct Item), compare);

double total_value = 0.0;


double total_weight = 0.0;

for (int i = 0; i < num_items; i++) {


if (items[i].weight <= capacity) {
total_value += items[i].value;
total_weight += items[i].weight;
capacity -= items[i].weight;
} else {
total_value += (double)(items[i].value * capacity) / items[i].weight;
total_weight += capacity;
capacity = 0;
}

if (capacity == 0)
break;
}

printf("Maximum value: %.2lf\n", total_value);

printf("item-weight\n");
for (int i = 0; i < num_items; i++) {
if (items[i].weight <= capacity) {
printf("%d-%d\n", i + 1, items[i].weight);
} else {
printf("%d-%.1lf\n", i + 1, (double)(items[i].value * capacity) / items[i].weight);
}

if (capacity == 0)
break;
}
}

int main() {
int num_items;
printf("Enter the number of items: ");
scanf("%d", &num_items);

struct Item items[num_items];

printf("Enter the weights of the items: ");


for (int i = 0; i < num_items; i++) {
scanf("%d", &items[i].weight);
}

printf("Enter the values of the items: ");


for (int i = 0; i < num_items; i++) {
scanf("%d", &items[i].value);
}

int capacity;
printf("Enter the maximum capacity of the knapsack: ");
scanf("%d", &capacity);

for (int i = 0; i < num_items; i++) {


items[i].ratio = (double)items[i].value / items[i].weight;
}
fractional_knapsack(items, num_items, capacity);

return 0;
}

Q3:

#include <stdio.h>
#include <stdlib.h>

int compare(const void *a, const void *b) {


return (*(int *)a - *(int *)b);
}

int minimum_computation_cost(int n, int arr[]) {


qsort(arr, n, sizeof(int), compare); // Sort the array in ascending order
int total_cost = 0;

while (n > 1) {
// Take the two smallest elements
int smallest1 = arr[0];
int smallest2 = arr[1];

// Calculate the cost of merging and add it to totalCost


total_cost += smallest1 + smallest2;

// Append the merged file size to arr


arr[1] = smallest1 + smallest2;

// Shift the elements in the array to remove the merged files


for (int i = 2; i < n; i++) {
arr[i - 1] = arr[i];
}

// Decrement the size of the array


n--;

// Sort the array again in ascending order


qsort(arr, n, sizeof(int), compare);
}

return total_cost;
}

int main() {
int n;
printf("Enter the size of the array: ");
scanf("%d", &n);

int arr[n];
printf("Enter the array elements: ");
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}

int cost = minimum_computation_cost(n, arr);


printf("Minimum computation cost to merge all files: %d\n", cost);

return 0;
}

WEEK 10: Solution

Q1:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
int index;
int startTime;
int finishTime;
} Activity;

int compare(const void *a, const void *b) {


return ((Activity *)a)->finishTime - ((Activity *)b)->finishTime;
}

void selectNonConflictingActivities(int n, int startTimes[], int finishTimes[]) {


Activity activities[n];
for (int i = 0; i < n; i++) {
activities[i].index = i + 1;
activities[i].startTime = startTimes[i];
activities[i].finishTime = finishTimes[i];
}

qsort(activities, n, sizeof(Activity), compare);

int count = 1;
int lastSelected = 0;

printf("No. of non-conflicting activities: %d\n", count);


printf("List of selected activities: %d", activities[lastSelected].index);

for (int i = 1; i < n; i++) {


if (activities[i].startTime >= activities[lastSelected].finishTime) {
count++;
lastSelected = i;
printf(", %d", activities[i].index);
}
}
printf("\n");
}

int main() {
int n;
printf("Enter the number of activities: ");
scanf("%d", &n);

int startTimes[n];
printf("Enter the starting times of the activities: ");
for (int i = 0; i < n; i++) {
scanf("%d", &startTimes[i]);
}

int finishTimes[n];
printf("Enter the finishing times of the activities: ");
for (int i = 0; i < n; i++) {
scanf("%d", &finishTimes[i]);
}

selectNonConflictingActivities(n, startTimes, finishTimes);

return 0;
}

Q2:

#include <stdio.h>

void selectTasks(int n, int timeTaken[], int deadlines[]) {


int count = 0;
int selectedTasks[n];
int totalTime = 0;

for (int i = 0; i < n; i++) {


if (totalTime + timeTaken[i] <= deadlines[i]) {
count++;
selectedTasks[count - 1] = i + 1;
totalTime += timeTaken[i];
}
}

printf("Max number of tasks = %d\n", count);


printf("Selected task numbers: ");
for (int i = 0; i < count; i++) {
printf("%d", selectedTasks[i]);
if (i != count - 1)
printf(", ");
}
printf("\n");
}

int main() {
int n;
printf("Enter the total number of tasks: ");
scanf("%d", &n);

int timeTaken[n];
printf("Enter the time taken for each task: ");
for (int i = 0; i < n; i++) {
scanf("%d", &timeTaken[i]);
}

int deadlines[n];
printf("Enter the deadlines for each task: ");
for (int i = 0; i < n; i++) {
scanf("%d", &deadlines[i]);
}

selectTasks(n, timeTaken, deadlines);

return 0;
}

Q3:

#include <stdio.h>
#include <stdlib.h>

void findMajorityAndMedian(int n, int elements[]) {


int count = 0;
int candidate = 0;

for (int i = 0; i < n; i++) {


if (count == 0) {
candidate = elements[i];
count = 1;
} else if (elements[i] == candidate) {
count++;
} else {
count--;
}
}

count = 0;
for (int i = 0; i < n; i++) {
if (elements[i] == candidate) {
count++;
}
}
printf("Majority element: %s\n", (count > n / 2) ? "yes" : "no");

// Sorting the array in ascending order


for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (elements[j] > elements[j + 1]) {
int temp = elements[j];
elements[j] = elements[j + 1];
elements[j + 1] = temp;
}
}
}

printf("Median: ");
if (n % 2 == 1) {
printf("%d\n", elements[n / 2]);
} else {
int median1 = elements[n / 2 - 1];
int median2 = elements[n / 2];
printf("%d\n", (median1 + median2) / 2);
}
}

int main() {
int n;
printf("Enter the size of the array: ");
scanf("%d", &n);

int elements[n];
printf("Enter the elements of the array: ");
for (int i = 0; i < n; i++) {
scanf("%d", &elements[i]);
}

findMajorityAndMedian(n, elements);

return 0;
}

WEEK 11: Solutions

Q1:

#include <stdio.h>
#include <limits.h>

#define MAX_SIZE 100

int matrixChainMultiplication(int n, int dimensions[][2]) {


int dp[MAX_SIZE][MAX_SIZE];
for (int i = 1; i <= n; i++) {
dp[i][i] = 0;
}

for (int chainLength = 2; chainLength <= n; chainLength++) {


for (int i = 1; i <= n - chainLength + 1; i++) {
int j = i + chainLength - 1;
dp[i][j] = INT_MAX;

for (int k = i; k < j; k++) {


int cost = dp[i][k] + dp[k+1][j] + dimensions[i-1][0] * dimensions[k][1] * dimensions[j][1];
if (cost < dp[i][j]) {
dp[i][j] = cost;
}
}
}
}

return dp[1][n];
}

int main() {
int n;
printf("Enter the number of matrices: ");
scanf("%d", &n);

int dimensions[MAX_SIZE][2];
printf("Enter the dimensions of the matrices:\n");
for (int i = 0; i < n; i++) {
scanf("%d %d", &dimensions[i][0], &dimensions[i][1]);
}

int minOperations = matrixChainMultiplication(n, dimensions);

printf("Minimum number of operations: %d\n", minOperations);

return 0;
}

Q2:

#include <stdio.h>

#define MAX_SIZE 100

int coinChange(int numCoins, int coinValues[], int N) {


int dp[MAX_SIZE] = {0};

dp[0] = 1;

for (int i = 0; i < numCoins; i++) {


for (int j = coinValues[i]; j <= N; j++) {
dp[j] += dp[j - coinValues[i]];
}
}

return dp[N];
}

int main() {
int numCoins;
printf("Enter the number of coins: ");
scanf("%d", &numCoins);

int coinValues[MAX_SIZE];
printf("Enter the values of the coins: ");
for (int i = 0; i < numCoins; i++) {
scanf("%d", &coinValues[i]);
}

int N;
printf("Enter the value of N: ");
scanf("%d", &N);

int numWays = coinChange(numCoins, coinValues, N);

printf("Number of ways: %d\n", numWays);

return 0;
}

Q3:

#include <stdio.h>
#include <stdbool.h>

#define MAX_SIZE 100

bool subsetSumPartition(int n, int set[]) {


int totalSum = 0;
for (int i = 0; i < n; i++) {
totalSum += set[i];
}

if (totalSum % 2 != 0) {
return false;
}

int targetSum = totalSum / 2;

bool dp[MAX_SIZE][MAX_SIZE] = {false};


dp[0][0] = true;

for (int i = 1; i <= n; i++) {


for (int j = 0; j <= targetSum; j++) {
dp[i][j] = dp[i-1][j];
if (j >= set[i-1]) {
dp[i][j] = dp[i][j] || dp[i-1][j-set[i-1]];
}
}
}

return dp[n][targetSum];
}

int main() {
int n;
printf("Enter the number of elements: ");
scanf("%d", &n);

int set[MAX_SIZE];
printf("Enter the elements of the set: ");
for (int i = 0; i < n; i++) {
scanf("%d", &set[i]);
}

bool isPossible = subsetSumPartition(n, set);

if (isPossible) {
printf("yes\n");
} else {
printf("no\n");
}

return 0;
}

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy