#include<bits/stdc++.h>
typedef long long int ll;
typedef unsigned long long int ull;
#define BIG_NUM 2000000000
#define MOD 1000000007
#define EPS 0.000000001
using namespace std;



#define NUM 450

typedef pair<int,int> P; //firstは最短距離、secondは頂点の番号


//辺を表す構造体{行先、容量、コスト、逆辺のインデックス}
struct Edge{
	Edge(int arg_to,int arg_capacity,int arg_cost,int arg_rev_index){
		to = arg_to;
		capacity = arg_capacity;
		cost = arg_cost;
		rev_index = arg_rev_index;
	}

	int to,capacity,cost,rev_index;
};

int V; //頂点数
vector<Edge> G[NUM]; //グラフの隣接リスト表現
int dist[NUM]; //最短距離
int pre_node[NUM],pre_edge[NUM]; //直前の頂点と辺
int num_day,num_item,num_limit;
int num_problem,num_student,num_bag;
int POW[9];
int price[8][8],problem[200][8],bag[200][8];
int dp[9][6561][9];
int min_cost[6561];
int bag_index[200],problem_index[200];



//fromからtoへ向かう容量capacity,コストcostの辺をグラフに追加する
void add_edge(int from,int to,int capacity,int cost){
	G[from].push_back(Edge(to,capacity,cost,G[to].size()));
	G[to].push_back(Edge(from,0,-cost,G[from].size()-1));
}

//sourceからsinkへの、流量flowの最小費用流を求める
//流せない場合は-1を返す
int min_cost_flow(int source,int sink,int flow){
	int ret = 0;
	while(flow > 0){
		//ベルマンフォード方により、source-sink間最短経路を求める
		for(int i = 0; i < V; i++)dist[i] = BIG_NUM;
		dist[source] = 0;
		bool update = true;
		while(update){
			update = false;
			for(int node_id = 0; node_id < V; node_id++){
				if(dist[node_id] == BIG_NUM)continue;
				for(int i = 0; i < G[node_id].size(); i++){
					Edge &e = G[node_id][i];
					if(e.capacity > 0 && dist[e.to] > dist[node_id]+e.cost){
						dist[e.to] = dist[node_id]+e.cost; //node_idを経由した方が早い場合
						pre_node[e.to] = node_id;
						pre_edge[e.to] = i;
						update = true;
					}
				}
			}
		}

		if(dist[sink] == BIG_NUM){
			//これ以上流せない
			return -1;
		}

		//source-sink間最短路に沿って目いっぱい流す
		int tmp_flow = flow;
		for(int node_id = sink; node_id != source; node_id = pre_node[node_id]){
			tmp_flow = min(tmp_flow,G[pre_node[node_id]][pre_edge[node_id]].capacity);
		}
		flow -= tmp_flow;
		ret += tmp_flow*dist[sink];
		for(int node_id = sink; node_id != source; node_id = pre_node[node_id]){
			Edge &e = G[pre_node[node_id]][pre_edge[node_id]];
			e.capacity -= tmp_flow;
			G[node_id][e.rev_index].capacity += tmp_flow;
		}
	}
	return ret;
}

void func(){

	//商品情報を取得
	for(int i = 0; i < num_day; i++){
		for(int k = 0; k < num_item; k++)scanf("%d",&price[i][k]);
	}

	scanf("%d %d %d",&num_problem,&num_student,&num_bag);

	for(int i = 0; i < num_problem; i++){
		for(int k = 0; k < num_item; k++)scanf("%d",&problem[i][k]);
	}

	for(int i = 0; i < num_bag; i++){
		for(int k = 0; k < num_item; k++)scanf("%d",&bag[i][k]);
	}

	for(int state = 0; state < POW[num_item]; state++)min_cost[state] = BIG_NUM; //部品をstateの組合せで買う時の最小コスト

	for(int day = 0; day <= num_day; day++){
		for(int state = 0; state < POW[num_item]; state++){
			for(int i = 0; i <= num_limit; i++)dp[day][state][i] = BIG_NUM;
		}
	}

	dp[0][0][0] = 0;
	min_cost[0] = 0;

	int next_state,next_cost,tmp;
	int work[num_item];
	stack<int> S;

	//stateの組合せの部品を揃える際の最小コストを求める
	for(int day = 0; day <= num_day-1; day++){
		for(int state = 0; state < POW[num_item]; state++){
			for(int bought_num = 0; bought_num <= num_limit; bought_num++){

				if(dp[day][state][bought_num] == BIG_NUM)continue;

				dp[day+1][state][0] = min(dp[day+1][state][0],dp[day][state][bought_num]);

				if(bought_num == num_limit)continue; //もう買えないので、dp値を伝播させたらSKIP

				//何をいくつ買ったかを計算
				tmp = state;
				for(int i = num_item-1; i >= 0; i--){
					S.push(tmp/POW[i]);
					tmp %= POW[i];
				}

				for(int i = 0; i < num_item; i++){
					work[i] = S.top();
					S.pop();
				}

				for(int i = 0; i < num_item; i++){
					if(work[i] == 2)continue;

					next_state = state+POW[i];
					next_cost = dp[day][state][bought_num]+price[day][i];

					dp[day][next_state][bought_num+1] = min(dp[day][next_state][bought_num+1],next_cost);
					min_cost[next_state] = min(min_cost[next_state],next_cost);
				}
			}
		}
	}

	for(int i = 0; i < NUM; i++)G[i].clear();

	int source = 0, sink = 1, empty_bag = 2, index = 3;

	//袋の採番
	for(int i = 0; i < num_bag; i++)bag_index[i] = index++;
	//課題の採番
	for(int i = 0; i < num_problem; i++)problem_index[i] = index++;

	//sourceから袋への辺張り
	add_edge(source,empty_bag,num_student,0); //★★空袋はいくつ割り当てても良い★★
	for(int i = 0; i < num_bag; i++){
		add_edge(source,bag_index[i],1,0);
	}

	//課題からsinkへの辺張り
	for(int i = 0; i < num_problem; i++){
		add_edge(problem_index[i],sink,1,0);
	}

	//袋と課題のマッチング
	int tmp_code;

	//空袋と全ての課題を繋ぐ
	for(int i = 0; i < num_problem; i++){
		tmp_code = 0;
		for(int k = 0; k < num_item; k++){
			tmp_code += problem[i][k]*POW[k];
		}
		if(min_cost[tmp_code] != BIG_NUM){
			add_edge(empty_bag,problem_index[i],1,min_cost[tmp_code]);
		}
	}

	//その他の袋と課題を繋ぐ
	bool FLG;

	for(int i = 0; i < num_bag; i++){
		for(int k = 0; k < num_problem; k++){
			FLG = true;
			for(int a = 0; a < num_item; a++){
				if(bag[i][a] > problem[k][a]){ //部品の超過は不可
					FLG = false;
					break;
				}
			}
			if(!FLG)continue;

			tmp_code = 0;
			for(int a = 0; a < num_item; a++){
				tmp_code += (problem[k][a]-bag[i][a])*POW[a];
			}
			if(min_cost[tmp_code] != BIG_NUM){
				add_edge(bag_index[i],problem_index[k],1,min_cost[tmp_code]);
			}
		}
	}

	V = index;

	printf("%d\n",min_cost_flow(source,sink,num_student));
}

int main(){

	for(int i = 0; i < 9; i++)POW[i] = pow(3,i);

	while(true){
		scanf("%d %d %d",&num_day,&num_item,&num_limit);
		if(num_day == 0 && num_item == 0 && num_limit == 0)break;

		func();
	}
	return 0;
}




Measured memory_bytes: 7056