import std.stdio, std.conv, std.string, std.bigint;
import std.math, std.random, std.datetime;
import std.array, std.range, std.algorithm, std.container, std.format;
string read(){ static string[] ss; while(!ss.length) ss = readln.chomp.split; string res = ss[0]; ss.popFront; return res; }

const long mod = 1_000_000_007;

void main(){
	int n = read.to!int;
	int m = read.to!int;
	
	long[] as;
	foreach(i; 0 .. n) as ~= read.to!long;
	
	long[] bs;
	foreach(j; 0 .. m) bs ~= read.to!long;
		
	long[long] aPos, bPos;
	foreach(i, a; as) aPos[a] = i;
	foreach(j, b; bs) bPos[b] = j;
	
	// そのマスに入れる最大値
	long[] limit;
	foreach(i; 0 .. n){
		foreach(j; 0 .. m){
			limit ~= min(as, bs);
		}
	}
	
	// xを減らしていくとき、その行・列・全体で入れるマスはいくつあるか
	int[] aCounts; // その行にすでに入っている個数
	int[] bCounts; // その列にすでに入っている個数
	int aCount; // 解禁されている行数
	int bCount; // 解禁されている列数
	foreach(i; 0 .. n) aCounts ~= 0;
	foreach(j; 0 .. m) bCounts ~= 0;
	aCount = 0;
	bCount = 0;
	
	long ans = 1;
	foreach_reverse(x; 1 .. n * m + 1){
		if(x in aPos && x in bPos){
			// そこの場所できまり
			ans *= 1;
			// その行および列は新たに解禁された
			aCount += 1;
			bCount += 1;
			// その行および列はあきが減る
			aCounts[aPos[x]] += 1;
			bCounts[bPos[x]] += 1;
		}
		else if(x in aPos && x !in bPos){
			// その行で、解禁済みの列のうち、あきをさがす
			ans *= bCount - aCounts[aPos[x]];
			ans %= mod;
			// その行は新たに解禁された
			aCount += 1;
			// その行はあきが減る
			aCounts[aPos[x]] += 1;
		}
		else if(x !in aPos && x in bPos){
			// その列で、解禁済みの行のうち、あきをさがす
			ans *= aCount - bCounts[bPos[x]];
			ans %= mod;
			// その列は新たに解禁された
			bCount += 1;
			// その列はあきが減る
			bCounts[bPos[x]] += 1;
		}
		else{
			// 全体で、解禁済みの行かつ解禁済みの列のうち、あきをさがす
			ans *= aCount * bCount - (n * m - x);
			ans %= mod;
		}
	}
	
	ans.writeln;
}
