Pages

Showing posts with label Matching. Show all posts
Showing posts with label Matching. Show all posts

Tuesday, September 15, 2009

Maximum/Minimum Weighted Bipartite Matching using Cycle Cancelling

Problem:

This is an extension to our maximum cardinality bipartite matching problem we introduced earlier. Imagine the same situation, we are given a bipartite graph G = (V,E) in which the vertices can be separated into two disjoint sets such that there are no edges between vertices belonging in the same set. Instead of weightless edges like our previous problem - we are now faced with weighted edges. The problem now is to match the vertices such that the total weight of the matched edges is as small (or as large) as possible whilst still retaining the maximum number of matchings. The figure below depicts this situation:



Applications:

Like the weightless version, we can compute the most optimal pairings between two disjoint sets. For example, let X represent a set of workers and Y to be a set of jobs. There is an edge (u,v) in E of worker u in X to job v in Y of weight W, which gives the productivity of the worker u given job v. If we were tasked to assign the workers to the set of jobs such that each worker is given exactly 1 job and we were also told to maximise the total productivity of the assignments then the answer is simply the maximum weighted matching in the bipartite graph. We can also define the edge weights to mean the converse, for example, the weight for edge (u,v) could mean how difficult worker u would find job v – in which our task could be to minimise the total difficulty whilst ensuring all workers are assigning to exactly 1 job. This does not complicate things as we simply need to reverse our operations to derive the opposite result.

Algorithm:

We need a new approach to tackle this problem. Augmenting the paths isn't enough as it doesn't take into account the weightings on the edge. One idea is to arbitrarily assign the maximum matching without consideration of the weights and then try to successively improve (i.e. maximise) the cost by finding a negative cycle an augmenting the matchings along this cycle. To represent this we calculate a modified graph G’, with edge weights representing the cost difference we will gain by matching a vertex x with another vertex y. In other words, for any u and v, we put a weight W on the edge from u to v, where W is defined as the cost we will gain if we gave the current partner of v to u, i.e. W = cost(u, matching[v]) – cost(u, matching[u]). Now we simply look for a negative cycle in G’ and augment the partners along the cycle to produce a better matching. How does this work? If we can find a negative cycle from a vertex u to itself, it means we can alternate partners to reduce the total cost whilst also keeping the same number of matched partners.

Consider the following bipartite graph:



And the matchings below:

 

The matching on the left has a weight of 15; the optimal minimum matching has a weight of 8 (shown on the right). Algorithmically, let’s assign the vertices on the top-left and bottom-left (A and B respectively) and the vertices on the top-right and bottom-right (C and D respectively). We compute the modified G’ edge weights of (A,B) and (B,A) – the cost of switching the partners around (take note their matched counterparts – i.e. the right side, is calculated implicitly). Proceeding to do this we yield:

W(A,B) = cost(A, matching[B]) – cost(A, matching[A]) = 6 – 5 = 1
W(B,A) = cost(B, matching[A]) – cost(B, matching[B]) = 2 – 10 = -8

Therefore a possible cycle could be from A -> matching[B] -> B -> matching[A] -> A. This yields a cost of W(A,B) + W(B,A) = 1 + (-8) = -7. Since this is a negative cycle we can save a total cost of up to 7 (you can verify this by hand) if we augment along this cycle. From this, A becomes matched to matching[B] (i.e. D) and B becomes matched to matching[A] (i.e. C). The matching on the right hand side diagram highlights this minimal matching. To actually find a negative cycle, an easy way is to use Floyd-Warshall’s algorithm which is illustrated in our implementation below. Additional book-keeping is required to keep track of the parents so we can back-track and augment the negative cycle path.

To summarise, our algorithm is as follows:

- Start with an initial perfect matching M
- While there is a negative cycle C on G’, augment along the negative cycle C to produce a better matching M’
- Return the matching M

Other Notes:

This problem can also be solved using the Hungarian algorithm (a famous combinatorial optimisation algorithm) or using minimum-cost flows. In fact, our cycle cancelling implementation is a specific subset of the minimum cost flow algorithm. The difference between our cycle cancelling algorithm and the one used for the general minimum cost flow problem is that we need to run a maximum flow algorithm to derive an initial network (here we simply assign the vertices to each other as we are guaranteed a perfect matching it’s a complete bipartite graph).

There are many variations on maximum (or minimum) weighted bipartite matching. The first variation is called the assignment problem where we are given an equal number of vertices on each side and the graph itself is complete (i.e. all the vertices link to each other between the two disjoint sets). The second variation we are given an uneven number of vertices on each side but the graph itself is still complete, here we proceed to reduce the problem into the first variation by adding dummy vertices to make the sides even. Any edges which go to these vertices have a weight of 0 and hence do not have an effect on the final answer. Another variation is where there are an uneven number of vertices and the graph isn’t complete – hence a perfect matching may not be possible, this is where the general minimum cost flow algorithm comes in which we will go over later.

Applying the Algorithm:

We briefly demonstrate how the algorithm is used to solve two algorithmic problems.


ACM ICPC Pacific Northwest Regionals 2004 (GoingHome)

URL: http://acm.tju.edu.cn/toj/showp1636.html

The problem can be easily reduced to the assignment problem by constructing a bipartite graph of houses and people. The edge weights are simply the Manhattan distance between house i and person j. We then just run our algorithm over the graph and we obtain our answer!

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int cost[111][111];      // cost of assigning house i to person j
int curMatchings[111];   // house i matched to person curMatchings[i]
int delta[111][111];   // the delta/cost graph
int parent[111][111];   // used for backtracking the cycle
int cycle[111];         // path of the cycle
int totalLen;         // length of the cycle

bool augmentingCycle(int N) {
   memset(delta,0,sizeof(delta));
   memset(parent,0,sizeof(parent));
   memset(cycle,-1,sizeof(cycle));
   // derive the delta graph
   for (int i = 0; i < N; i++) {
      for (int j = 0; j < N; j++) {
         delta[i][j] = cost[i][curMatchings[j]] - cost[i][curMatchings[i]];
         parent[i][j] = j;
      }
   }
   for (int k = 0; k < N; k++) {
      for (int i = 0; i < N; i++) {
         for (int j = 0; j < N; j++) {
            if (delta[i][k] + delta[k][j] < delta[i][j]) {
               // minimum cost matching
               delta[i][j] = delta[i][k] + delta[k][j];
               // book-keep the optimal path so far for i->j
               parent[i][j] = parent[i][k];
               // detect for cycle (if vertex i and j are the same vertex)
               if (i == j) {
                  totalLen = 0;
                  // backtrack and construct the cycle path
                  do {
                     cycle[totalLen++] = i;
                     i = parent[i][j];
                  } while (i != j);
                  return true;
               }
            }
         }
      }
   }
   // did not find an augmenting path
   return false;
}

int cycleCancel(int N) {
   int res = 0;
   // pseudo max-flow for assignment problem
   for (int i = 0; i < N; i++) curMatchings[i] = i;
   while (augmentingCycle(N)) {
      // augment the negative cycle
      int r = curMatchings[cycle[0]];
      for (int i = 0; i < totalLen - 1; i++) {
         curMatchings[cycle[i]] = curMatchings[cycle[i+1]];
      }
      curMatchings[cycle[totalLen-1]] = r;
   }
   // compute the final cost
   for (int i = 0; i < N; i++) {
      res += cost[i][curMatchings[i]];
   }
   return res;
}

int main() {
   int N, M;
   while (cin >> N >> M) {
      if (N == 0 && M == 0) break;
      char ch;
      vector<pair<int,int> > houses;
      vector<pair<int,int> > people;
      for (int i = 0; i < N; i++) {
         for (int j = 0; j < M; j++) {
            cin >> ch;
            if (ch == 'H') houses.push_back(make_pair(i,j));
            else if (ch == 'm') people.push_back(make_pair(i,j));
         }
      }
      // compute the weights associated with matching house i to person j
      for (int i = 0; i < houses.size(); i++) {
         for (int j = 0; j < people.size(); j++) {
            cost[i][j] = abs(houses[i].first - people[j].first) + 
               abs(houses[i].second - people[j].second);
         }
      }
      cout << cycleCancel(houses.size()) << "\n";
   }
   return 0;
}

TC SRM387 D2 1000 (MarblesRegroupingHard)
URL: http://www.topcoder.com/stat?c=problem_statement&pm=8538

This is a re-visit of a previous problem we solved using DP. This is a slightly less obvious reduction than the previous problem; we need to compute the total number of colours which exists in the whole dataset. Then we can draw an edge from box i to colour j with a total weight of all the other j-coloured marbles from other boxes minus the ones in box i with colour j (as they don’t need to be moved). Take note that there may be a different number of colours to the number of boxes but we are always guaranteed that the number of colours is less than or equal to the number of boxes, hence a matching is always possible based on the pigeonhole principle. We can add dummy colours to the graph to reduce it to the assignment problem – which is what is done below:

class MarblesRegroupingHard {
public:
   int minMoves(vector <string>);
};

int totColours[15];
int B[51][15];

int cost[55][55];      // cost of assigning house i to person j
int curMatchings[55];   // house i matched to person curMatchings[i]
int delta[55][55];   // the delta/cost graph
int parent[55][55];   // used for backtracking the cycle
int cycle[55];         // path of the cycle
int totalLen;         // length of the cycle

bool augmentingCycle(int N) {
   memset(delta,0,sizeof(delta));
   memset(parent,0,sizeof(parent));
   memset(cycle,-1,sizeof(cycle));
   // derive the delta graph
   for (int i = 0; i < N; i++) {
      for (int j = 0; j < N; j++) {
         delta[i][j] = cost[i][curMatchings[j]] - cost[i][curMatchings[i]];
         parent[i][j] = j;
      }
   }
   for (int k = 0; k < N; k++) {
      for (int i = 0; i < N; i++) {
         for (int j = 0; j < N; j++) {
            if (delta[i][k] + delta[k][j] < delta[i][j]) {
               // minimum cost matching
               delta[i][j] = delta[i][k] + delta[k][j];
               // book-keep the optimal path so far for i->j
               parent[i][j] = parent[i][k];
               // detect for cycle (if vertex i and j are the same vertex)
               if (i == j) {
                  totalLen = 0;
                  // backtrack and construct the cycle path
                  do {
                     cycle[totalLen++] = i;
                     i = parent[i][j];
                  } while (i != j);
                  return true;
               }
            }
         }
      }
   }
   // did not find an augmenting path
   return false;
}

int cycleCancel(int N) {
   int res = 0;
   // pseudo max-flow for assignment problem
   for (int i = 0; i < N; i++) curMatchings[i] = i;
   while (augmentingCycle(N)) {
      // augment the negative cycle
      int r = curMatchings[cycle[0]];
      for (int i = 0; i < totalLen - 1; i++) {
         curMatchings[cycle[i]] = curMatchings[cycle[i+1]];
      }
      curMatchings[cycle[totalLen-1]] = r;
   }
   // compute the final cost
   for (int i = 0; i < N; i++) {
      res += cost[i][curMatchings[i]];
   }
   return res;
}


int MarblesRegroupingHard::minMoves(vector <string> boxes) {
   int res = 0;
   memset(totColours,0,sizeof(totColours));
   memset(B,0,sizeof(B));
   int numColours = 0;
   for (int i = 0; i < boxes.size(); i++) {
      istringstream iss(boxes[i]);
      int colours;
      int k = 0;
      while (iss >> colours) {
         totColours[k] += colours;
         B[i][k++] = colours;
      }
      numColours = k;
   }
   // make the weighted bipartite graph
   // use the cost matrix to fill in
   for (int i = 0; i < boxes.size(); i++) {
      for (int j = 0; j < boxes.size(); j++) {
         if (j >= numColours) cost[i][j] = 0;
         else cost[i][j] = totColours[j] - B[i][j];
      }   
   }
   return res = cycleCancel(boxes.size());
}

Thursday, September 10, 2009

TC TCO06 Round 1 D1 350 (SeparateConnections)

Category: Dynamic Programming, Matching in a General Graph
URL: http://www.topcoder.com/stat?c=problem_statement&pm=6095

This problem just asks us to find the maximum number of matches in a general graph (should be a straightforward reduction given the problem constraints). The most efficient way to solve this problem is to use a maximum matching algorithm (like Edmond's). However, this is not straightforward to code and certainly not required for a 350 pointer problem. Given the constraints, one easy way to implement a maximum matching algorithm is to use dynamic programming. We will be using a bitmask to efficiently keep track of the nodes that have already been matched so far.

For those new to bitmasks, here is a very short tutorial on it. As you may know, all decimal integer numbers (base 10) have a binary representation form (base 2). As a result, each bit inside a binary representation can represent up to 1-bit of information (duh). Besides representing numbers we can use each of these bits (0 or 1) as useful book-keeping information - in our case (and in many cases) we use them to mark whether or not we have visited or used a particular resource. So for example, the bit representation 01100 could mean we have used resource 2 and 3 (0-based index). Note that we go from left to right due to the fact that the left-most bit is also the most significant bit (MSB). This bit representation (01100) is simply known as 2^3 + 2^2 = 14 in decimal. Expanding on this, let's say in our problem we have used node 1, node 4 and node 5. This is represented as 2^5 + 2^4 + 2^1 = 32 + 16 + 2 = 50. There are several important bit operations we can use to manipulate and query the bits:

For starters, to get a '1' in the n-th position from the right (remember 0-based index) - you use the shift left operator '<<'. For example, 1 << 5 yields a bit representation equivalent to 0010 0000 (32).

To set a bit, we use the bitwise-OR operator denoted as '|'. The bitwise-OR operator works by applying the OR operation to each of the corresponding bits between two numbers. The OR operation is defined as:

0 OR 0 = 0
0 OR 1 = 1
1 OR 0 = 1
1 OR 1 = 1

For example to set the 5th bit (again 0-index) from the right, we first use the shift left operator to yield the correct number (i.e. 1 << 5). Let's say the bitmask before the operation is defined as 0100 0010 (68). Then applying 1 << 5 which has a representation of 0010 0000 (32) yields:

0100 0010 (68) OR 0010 0000 (32) Equals 0110 0010 (100)

To test whether a bit is set within a bitmask, we make use of the bitwise-AND operator denoted as '&'. The AND operation is defined as:

0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 AND 1 = 1

We use a similar method as we did for setting a bit in a particular position, let's say we wanted to test if the 5th bit was set. We use the shift left operator to yield the correct number (i.e. 1 << 5). Then we simply apply the bitwise-AND operator to the bitmask we wanted to test. If our mask was 0110 0010 then it would return 0010 0000. However if our mask was 0100 0010, then it would return 0000 0000 as it has no bits both turned on at the same position. Thus, we can use this to test whether or not a bitmask contains a bit in a particular position, if it the result is positive after the AND operation then it is inside the bitmask, if it's zero then it isn't inside the bitmask.

There are other bitmasking techniques which won't be discussed here as they aren't used for this problem.

Back to the problem, given that there are only 18 nodes at maximum. We can easily create a bitmask that keeps track of which nodes we have currently used up (there are 2^18 = 262144 different combinations). So for the actual DP state, we can iteratively introduce more nodes to the set and choose any adjacent nodes which haven't been used so far. Therefore, the state is D(n,m) where n is the index of which node we are processing and m is the bitmask of which nodes we have already used up. Next, we need to define the recurrence relation as well as any base cases. The recurrence relation can be found by selecting from a set of valid decisions at a particular node.

The two available options are to:
- Not used the node in the maximum matching (in hopes that it increases the available nodes later on)
- Use the node along with another node which is adjacent to it (increases the number of matchings by 1 and decreases the available set)

We define the recurrence as: D(n,m) = max { 1 + D(n+1, m | (1 << k), D(n+1, m) } where k is the set of all unused nodes which are adjacent to n. Take note, we do not consider matching node n, if it's already been matched by the time we reach index n. Then applying a simple memoization we yield a working solution:

class SeparateConnections {
public:
   int howMany(vector <string>);
};

int adjmat[19][19];
int memo[19][1<<19];
int sz;

int func(int idx, int mask) {
   if (idx >= sz) return 0;
   if (memo[idx][mask] != -1) return memo[idx][mask];
   int res = 0;
   if (!(mask & (1 << idx))) {
      for (int i = idx+1; i < sz; i++) {
         if (mask & (1 << i)) continue;
         if (!adjmat[idx][i]) continue;
         res >?= 1 + func(idx+1, mask | (1 << i));
      }
   }
   res >?= func(idx+1, mask);
   return memo[idx][mask] = res;
}

int SeparateConnections::howMany(vector <string> mat) {
   int res = 0;
   for (int i = 0; i < mat.size(); i++) {
      for (int j = 0; j < mat.size(); j++) {
         if (mat[i][j] == 'Y') adjmat[i][j] = adjmat[j][i] = 1;
      }
   }
   sz = mat.size();
   memset(memo,-1,sizeof(memo));
   return res = func(0,0) * 2;
}

However, one can also use a general matching algorithm like Edmond's Blossoming/Shrinking Algorithm to solve the problem. See below for such an implementation (based on Pape and Conradt variation of Edmond's which is incorrect for a certain set of inputs, but it still passed system tests for this problem). No further explanation would be provided for this algorithm as I'll leave this for a later blog entry in which we build up our matching algorithms.

#define MAX_VERTICES 21

int adjmat[MAX_VERTICES+1][MAX_VERTICES+1];
int table[MAX_VERTICES+1][MAX_VERTICES+1];
int mate[MAX_VERTICES+1];
int level[MAX_VERTICES+1];
int grandparent[MAX_VERTICES+1];

bool checkAncestor(int currentNode, int checkNode) {
       int v = grandparent[currentNode];
       if (v < 0) return false;   // not an ancestor
       if (v == checkNode) return true; // ancestor
       return checkAncestor(v, checkNode);
}

int augment(int curNode) {
       // BFS an augmenting path
       memset(level,1,sizeof(level));
       memset(grandparent,-1,sizeof(grandparent));
       queue<int> q;
       q.push(curNode);
       level[curNode] = 0;
       while (!q.empty()) {
               int x = q.front(); q.pop();
               for (int y = 1; y <= MAX_VERTICES; y++) {
                  // not part of an edge in G or seen the odd-vertex in T
                  if (!adjmat[x][y] || !level[y]) continue;   
                  if (mate[y] == 0) {
                     // found an augmenting path
                     int next = 0;
                     while (true) {
                        mate[y] = x;
                        next = mate[x];
                        mate[x] = y;
                        if (grandparent[x] < 0) break;
                        x = grandparent[x];
                        mate[next] = x;
                        y = next;
                     }
                     return 1;
                 } else if (mate[y] != 0 && !checkAncestor(x,y) && 
                         mate[y] != x) {
                     // push the node into the alternating tree T
                     q.push(mate[y]);
                     grandparent[mate[y]] = x;
                     level[y] = 0;
                 }
               }
       }
       return 0;
}

int maxMatching() {
   int res = 0;
   memset(mate,0,sizeof(mate));    // all currently unmatched
   memset(grandparent,-1,sizeof(grandparent));
   // start at the vertex i if it's single
   while (true) {
      int v = 0;
      for (int i = 1; i < MAX_VERTICES; i++) {
         if (!mate[i]) v += augment(i);
      }
      if (v == 0) break;
      res += v;
   }
   return res;
}

int SeparateConnections::howMany(vector <string> mat) {
   for (int i = 0; i < mat.size(); i++) {
      for (int j = 0; j < mat.size(); j++) {
         if (mat[i][j] == 'Y') adjmat[i+1][j+1] = adjmat[j+1][i+1] = 1;
      }
   }
   return maxMatching() * 2;
}

Wednesday, August 26, 2009

Bipartite Matching Algorithm

The problem:

Given a graph G=(V,E) where G is a bipartite graph. Then G can be separated into two disjoint vertex sets A and B where there exists no edges (u,v) where both u and v belong to the same vertex set. We define a matching M to be a set of edges from A to B where each vertex V belongs to at most one such edge. Then the maximum matching refers to the highest possible cardinality of M following the rules we defined above.

Applications:

We can define X to represent a set of workers and Y to be a set of jobs. There is an edge (u,v) in E iff worker u in X is sufficiently skilled enough to work in job v in Y. If we were tasked to assign the maximum number of workers to exactly 1 job then the answer is simply the maximum matching of the graph G.

Algorithm:

Define an alternating path P as a path which alternates between the two sets of vertices A and B. More specifically the alternating P follows the form of: (u0, v0), (v0, u1), (u1, v1), (v1, u2) etc. where each uX is in A and vY is in B. An augmenting path is an alternating path with each (uX, vX) edge being matched (let's define this to be colour blue) and each (vX, u(X+1)) edge being unmatched (define this to be colour red). Recall that one vertex can only be matched to one edge. One special note about an augmenting path is that it ends with an unmatched (uX, vX) edge and we can simply alternate the matchings along the path to obtain exactly 1 more matching in the graph.

For example, take:

A --- B ___ C --- D ___ E --- F ___ G --- H

Where --- denotes an unmatched edge and ___ denotes a matched edge. If H isn't in the matching M then we can alternate:

A ___ B --- C ___ D --- E ___ F --- G ___ H

to obtain 1 more matching (3 vs 4 matchings).

Then our algorithm is to keep finding augmenting paths and improving the number of matchings iteratively. When there are no more augmenting paths we are done.

Theorem: If there exists no augmenting paths then the number of matchings is optimal.

(Informal) Proof: Assume there are no augmenting paths in M. To increase the number of matchings we need to match a new edge (that is currently coloured red). As there are no augmenting paths there exists no red edges from a vertex x in A to a vertex y in B where vertex y is currently unmatched (otherwise there would be an augmenting path). Now if we introduce a new matching into M, we will revert the changes done by an augmenting path:

... C ___ D --- E ___ F --- G ___ H

Matching F and G would force:

... C ___ D --- E --- F ___ G --- H

The subset is the same as our previous alternating path which wasn't augmented. Therefore, introducing a new matching with no augmenting path present does not increase the total number of matchings. Therefore, the absence of augmenting paths means the number of matchings is optimal.

Other Notes:

To find the actual augmenting path, a simple DFS will suffice. A simple way to implement this is to keep track of the colours of each edge (inside the adjacency matrix where red colour = 1, blue colour = 2) as well as keeping track of which side we are on to know which colour edges we need to traverse. We only traverse red edges when we are on the left side and we only traverse blue edges when we are on the right side. Note that if there are no blue edges then we have found an augmenting path (as the right side vertex is unmatched).

Another note is that the number of matchings never goes down so once a vertex is matched in iteration i, then it is also matched in iteration i+1 - although the actual vertex it is matched to can change. Furthermore, we can derive which vertices are matched together simply by looking at the matching array. For vertex i (on the left side) it is matched to matching[i] on the right side, -1 if it isn't matched. For more details, see the implementation below (in the class used for SPOJ TAXI).

The maximum flow algorithm can be reduced to the bipartite matching problem, assign a super source to the set of vertices in A and assign a super sink to the set of vertices in B. Let each edge between A and B have a capacity of 1, and each edge between the source and A as well as B and the sink to have a capacity of infinity. Then the maximum matching is simply the maximum flow from the source to the sink.

Applying the Algorithm (SPOJ TAXI):

To apply the matching algorithm, let's take an easy example. This problem statement can be accessed via:
http://spoj.pl/problems/TAXI

The problem asks us to assign a taxi to exactly 1 passenger. There's a time limit where each taxi must reach the passenger by, this criterion is the basis on whether there is an edge between taxi i and passenger j. We asked to find the maximum number of matchings we can make between the taxi to the passengers. All we need to do is construct the bipartite graph and run the matching algorithm to print out the results.

The implementation is shown below:


#include <iostream>
#include <string>
#include <algorithm>
#include <sstream>
#include <vector>

using namespace std;

class BipartiteMatching {
private:
vector<vector<int> > adjmat;
vector<int> matched;
vector<int> visited;

int N;
int M;

bool augment(int node, int side) {
visited[side*N+node] = 1;
if (!side) {
// left side
for (int i = 0; i < M; i++) {
if (adjmat[node][i] == 1 && !visited[i+N]) {
// check for right-side unmatched node
if (matched[i+N] == -1) {
adjmat[node][i] = 2;
matched[i+N] = matched[node] = i;
return true;
}
// otherwise dfs more
bool f = augment(i, 1-side);
if (f) {
// augmenting path found
adjmat[node][i] = 2;
matched[i+N] = matched[node] = i;
return true;
}
}
}
return false;
} else {
// right side
for (int i = 0; i < N; i++) {
if (adjmat[i][node] == 2 && !visited[i]) {
bool f = augment(i, 1-side);
if (f) {
// augmenting path found
adjmat[i][node] = 1;
return true;
}
}
}
return false;
}
return false;
}

public:
BipartiteMatching(int _N, int _M) : N(_N), M(_M) {
// initialise the data structures
adjmat = vector<vector<int> >(N, vector<int>(M, 0));
visited = vector<int>(N+M,0);
matched = vector<int>(N+M,0);
}

void Reset() {
for (int i = 0; i < adjmat.size(); i++) {
fill(adjmat[i].begin(),adjmat[i].end(),0);
}
}

void AddEdge(int i, int j) {
adjmat[i][j] = 1;
}

int maxMatching() {
int res = 0;
bool valid = true;
// all vertices are unmatched at the start
for (int i = 0; i < N + M; i++) matched[i] = -1;
// while there is at least 1 valid augmenting path keep
// iterating
while (valid) {
valid = false;
for (int i = 0; i < N; i++) {
if (matched[i] == -1) {
fill(visited.begin(),visited.end(),0);
bool f = augment(i,0);
if (f) valid = true;
}
}
}
// count the number of matches
for (int i = 0; i < N; i++) {
if (matched[i] != -1) { res++; }
}
return res;
}
};

class point {
public:
int x;
int y;
point() { }
point(int _x, int _y) : x(_x), y(_y) { }
};

bool operator<(const point& lhs, const point& rhs) {
if (lhs.x != rhs.x) return lhs.x < rhs.x;
if (lhs.y != rhs.y) return lhs.y < rhs.y;
return false;
}

int main(int argc, char** argv) {
ios_base::sync_with_stdio(false);
int numTest = 0;
cin >> numTest;
int p, t, s, c;
BipartiteMatching* bpm = new BipartiteMatching(406,206);
while (numTest-- && cin >> p >> t >> s >> c) {
vector<point> people;
vector<point> taxi;
// people
int x, y;
while (p-- && cin >> x >> y) {
people.push_back(point(x,y));
}
while (t-- && cin >> x >> y) {
taxi.push_back(point(x,y));
}
// make graph
bpm->Reset();
for (int i = 0; i < people.size(); i++) {
for (int j = 0; j < taxi.size(); j++) {
long long dist = abs(people[i].x-taxi[j].x)+
abs(people[i].y-taxi[j].y);
if (dist*200 <= c*s) {
// can reach
bpm->AddEdge(i,j);
}
}
}
// calculate maximum bipartite matching
cout << bpm->maxMatching() << "\n";
}
return 0;
}