//2361658 2010-11-30 14:10:56 Accepted 1031 C 490 160 VRS
//1031 拿火柴 除正方形
//贪心+搜索+剪枝+位操作 (其实也是最小二分覆盖问题,不过没用二分匹配去做)
//首先生成火柴跟正方形的对应关系,并用64位保存,由于n<=5,所以火柴最多60根,正方形最多55个
//然后,去除当前不存在的火柴,并通过可去除正方形数(count)对stick进行排序,贪心先拿覆盖最多的火柴;
//然后深搜,同时利用到两种剪枝
//不过看网上说,测试只用到n=4,我这个方法n=5时就很慢。。。- -
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<time.h>
#define bool int
typedef struct _STICK
{
long long squares;
bool status;
int count; //可除正方形数
}STICK;
STICK stick[65];
STICK stickResult;
//由于把所有正方形保存在long long (64位)中,所以不同段记录不同边长的正方形
long long resFinish[6];
int posBit[6]={0,0,25,41,50,54};
int n;
int minStickNum;
//time_t timeA,timeB;
int deleteNum;
int cmp(const void *x,const void *y)
{
STICK *a=(STICK *)x;
STICK *b=(STICK *)y;
return b->count-a->count;
}
//对火柴stickNo增加一个可去除的正方形
void BuildBitData(int row,int col,int RorC,int stickNo,int level)
{
if(row<=0 || row+level>n+1 || col<=0 || col>n-level+1)
return;
int temp,loc;
if(RorC){ temp=row; row=col; col=temp; }
loc=(row-1)*(n-level+1)+col;
long long tempBit=1;
tempBit<<=(posBit[level]+loc-1);
stick[stickNo].squares |= tempBit;
stick[stickNo].count++;
}
//生成火柴与正方形的对应关系
//分两类火柴,横放和竖放,两者是关于对角线对称的,所以只需要计算其一就行
//这里先算出该火柴的row和col,又先假设正方形由二维定义位置,根据推导知道等于: 【j为当前正方形边长】
// (row-j,col-j+1),(row-j,col-j+2)...(row-j,col);(row,col-j+1),(row,col-j+2)...(row,col)
//再转换为一维的表示,最后转为位记录
void BuildLink()
{
int i,j,k,row,col;
int stickNum;
bool stick_RorC;
stickNum=2*n*(n+1);
for(i=1;i<=stickNum;i++)
{
if((i-1)%(2*n+1)<n) //注意刚好整除的边界问题,当i刚好等于2n*(n+1)时,模为0
{
stick_RorC=0;
col=i%(2*n+1);
row=i/(2*n+1)+1;
}
else
{
stick_RorC=1;
col=(i-1)/(2*n+1)+1; //同上注意
row=(i-1)%(2*n+1)-n+1;
}
for(j=1;j<=n;j++)
{
for(k=col-j+1;k<=col;k++)
{
BuildBitData(row-j,k,stick_RorC,i,j);
BuildBitData(row,k,stick_RorC,i,j);
}
}
}
}
//先生成最终结果的位值
void BuildResultFinish()
{
resFinish[1]=(long long)1;
resFinish[2]=((long long)1<<25)|15;
resFinish[3]=((long long)1<<41)|((long long)15<<25)|511;
resFinish[4]=((long long)1<<50)|((long long)15<<41)|((long long)511<<25)|65535;
resFinish[5]=((long long)1<<54)|((long long)15<<50)|((long long)511<<41)|((long long)65535<<25)|33554431;
}
//可行性剪枝操作
//opt[i] = si & s(i+1) & s(i+2) … & sn 如果剩下的都不能产生结果,就剪掉
bool Option(int k)
{
long long tempBit=0;
int i;
for(i=k;i<2*n*(n+1)-deleteNum;i++)
tempBit |= stick[i].squares;
if(resFinish[n]!=(stickResult.squares | tempBit))
return 1;
else
return 0;
}
//回溯,搜索dfs
void Backtrack(int k,int currNum)
{
STICK stickTemp;
//用了两种剪枝操作
//1.当前值大于等于阶段最优解,剪枝
//2.可行性剪枝
if(k==2*n*(n+1)-deleteNum || currNum>=minStickNum || Option(k))
return ;
else
{
stickTemp.squares=stickResult.squares;
if(stick[k].status==0)
{
stickResult.squares |= stick[k].squares;
if(stickResult.squares==resFinish[n])
{
if(currNum+1<minStickNum)
minStickNum=currNum+1;
stickResult.squares=stickTemp.squares;
return;
}
Backtrack(k+1,currNum+1);
stickResult.squares=stickTemp.squares;
}
Backtrack(k+1,currNum);
}
}
//优化操作1
//如果si与sj的效果一样,去掉火柴j
void DeleteSameData()
{
int i,j;
deleteNum=0;
long long tempBit=0;
for(i=1;i<=2*n*(n+1);i++)
{
tempBit=stick[i].squares | stickResult.squares;
for(j=i+1;j<=2*n*(n+1);j++)
if(tempBit==stick[j].squares)
{
stick[j].count=-1;
deleteNum++;
}
}
}
int main()
{
int testcase;
int inputNum,i,inputData;
BuildResultFinish();
scanf("%d",&testcase);
while(testcase--)
{
memset(stick,0,sizeof(stick));
stickResult.squares=0;
minStickNum=65;
scanf("%d\n%d",&n,&inputNum);
BuildLink();
for(i=0;i<inputNum;i++)
{
scanf("%d",&inputData);
stickResult.squares |= stick[inputData].squares;
stick[inputData].status=1;
}
DeleteSameData();
// time(&timeA);
//优化2,贪心,从可去除最多个正方形的火柴开始取起
qsort(stick,2*n*(n+1)+1,sizeof(stick[0]),cmp);
Backtrack(0,inputNum);
// time(&timeB);
printf("%d\n",minStickNum-inputNum);
// printf("时间是:%d\n",timeB-timeA);
}
return 0;
}