from functools import lru_cache
from typing import List
class DlxRepeatOvelap:
# max_node_num是节点池的大小,取1节点大小即可, row_num和col_num是矩阵的行数和列数
# one_points是1节点的坐标的二元组(ii, jj)的列表,行坐标和列坐标都是从1开始(注意不是从0开始)
def __init__(self, max_node_num, row_num, col_num, one_points: List):
max_node_num += col_num+1 # 加上哨兵的节点数
self.__row_num = row_num
self.__col_num = col_num
self.__pos = 0 # 当前空闲的末尾位置
# 上下左右后继的节点id
self.uu, self.dd, self.ll, self.rr = [0]*max_node_num, [0]*max_node_num, [0]*max_node_num, [0]*max_node_num
self.row, self.col = [0] * max_node_num, [0] * max_node_num # 节点的行和列
self.one_num = [0] * (self.__col_num + 1) # 列上的1计数值
self.one_points = one_points
self.__boot()
# 十字链表状态初始化
def __boot(self):
one_points = self.one_points
one_points.sort()
# 最上面一行0号节点到col_num号节点都是哨兵, 都是链表头,不是实际1节点,0号节点下面不会接其他节点,只作为是横向的哨兵
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
col_num, col = self.__col_num, self.col
for jj in range(col_num + 1):
ll[jj] = jj - 1
rr[jj] = jj + 1
uu[jj] = jj
dd[jj] = jj
col[jj] = jj
ll[0], rr[col_num] = col_num, 0
self.__pos = col_num + 1
# 逐行添加节点
i = 0
n = len(one_points)
row, col, one_num = self.row, self.col, self.one_num
while i < n:
j = i
h_node = None # 一行的第一个节点
while j < n and one_points[j][0] == one_points[i][0]:
ii, jj = one_points[j]
pos = self.__pos
row[pos], col[pos] = ii, jj
one_num[jj] += 1
uu[pos], dd[pos] = jj, dd[jj]
uu[dd[jj]] = pos
dd[jj] = pos
# 新节点总是放在头节点的右边
if h_node is None:
h_node = pos
ll[h_node], rr[h_node] = h_node, h_node
else:
ll[pos], rr[pos] = h_node, rr[h_node]
ll[rr[h_node]] = pos
rr[h_node] = pos
j += 1
self.__pos += 1
i = j
# c节点所在的列上除了c节点以外,其他的节点都在水平方向断开
def remove_col(self, c):
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
node = dd[c]
while node != c:
ll[rr[node]] = ll[node]
rr[ll[node]] = rr[node]
node = dd[node]
# c节点所在的列上除了c节点以外,其他的节点都在水平方向接回来
def resume_col(self, c):
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
node = uu[c]
while node != c:
ll[rr[node]] = node
rr[ll[node]] = node
node = uu[node]
# 估价函数,预估最少还需要选择多少行才能完全覆盖所有列
def score(self):
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
overlap = [0] * (self.__col_num + 1)
cnt = 0
node = rr[0]
while node != 0:
if overlap[node] == 1:
node = rr[node]
continue
cnt += 1
overlap[node] = 1
# 该列的所有行全部都选中
mm = dd[node]
while mm != node:
nn = rr[mm]
while nn != mm:
overlap[self.col[nn]] = 1
nn = rr[nn]
mm = dd[mm]
node = rr[node]
return cnt
# DFS找最小的可行解
def dfs(self, step, max_step, path: List):
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
if step + self.score() > max_step:
return False
if rr[0] == 0:
return True
# 选1最少的行
c = rr[0]
min_one_num = 0x7fffffff
min_col = None
while c != 0:
if self.one_num[c] < min_one_num:
min_one_num = self.one_num[c]
min_col = c
c = rr[c]
# 针对min_col列,枚举能够选择的行
node = dd[min_col]
while node != min_col:
nn = rr[node]
while nn != node:
self.remove_col(nn)
nn = rr[nn]
self.remove_col(node)
path.append(self.row[node])
if self.dfs(step+1, max_step, path):
return True
path.pop(-1)
self.resume_col(node)
nn = ll[node]
while nn != node:
self.resume_col(nn)
nn = ll[nn]
node = dd[node]
return False
# 迭代加深获取一个最小的可行解
def get_one_minimal_solution(self):
path = []
for bound in range(1, self.__col_num + 1):
if self.dfs(0, bound, path):
return path
return None
T = int(input())
for _ in range(T):
n = int(input())
arr = list(map(int, input().split()))
miss_arr = arr[1:]
# 生成每一个边长是kk的正方形包含的火柴编号
rect_arr = []
for kk in range(1, n+1):
for i in range(5):
if i + kk > n:
break
for j in range(5):
if j + kk > n:
break
# 左上角在i, j, 边长是kk的正方形
arr = []
val = 1 + (2*n+1) * i + j # 第一根火柴
for ii in range(kk):
# 上面一排火柴
arr.append(val + ii)
val = 1 + (2*n+1) * i + j + n #左侧第一根
for ii in range(kk):
arr.append(val + ii * (2*n+1))
val = 1 + (2*n+1) * i + j + n + kk # 右侧第一根
for ii in range(kk):
arr.append(val + ii * (2*n+1))
val = 1 + (2*n+1) * i + j + (2*n+1) * kk # 下侧第一根
for ii in range(kk):
arr.append(val + ii)
rect_arr.append(arr)
del_id_set = set()
for stick in miss_arr:
for idx, arr in enumerate(rect_arr):
if stick in arr:
del_id_set.add(idx)
stick_arr = [i for i in range(1, (2*n+1) * n + n + 1) if i not in miss_arr]
rect_arr = [arr for idx, arr in enumerate(rect_arr) if idx not in del_id_set]
row2stick_id = {}
stick_id2row = {}
for idx, stick_id in enumerate(stick_arr):
stick_id2row[stick_id] = idx+1
one_points = []
for rect_id in range(1, len(rect_arr) + 1):
stick_arr = rect_arr[rect_id - 1]
for stick in stick_arr:
row_id = stick_id2row[stick]
one_points.append((row_id, rect_id))
row_num, col_num = len(stick_id2row), len(rect_arr)
algo = DlxRepeatOvelap(row_num * col_num, row_num, col_num, one_points)
ret = algo.get_one_minimal_solution()
if ret is not None:
print(len(ret))
else:
print('0')