题目
题目描述
有一个由火柴棒作为边组成的
的格子。(
)按照下图,给火柴棒编号。(这是
的情况,其它情况类似)
现在将移除某些火柴棒的状态作为初始状态,需要再移除一些火柴棒,以保证图中一个正方形也没有。请求出所有需要移除火柴棒的最少根数。
输入格式
第一行一个整数 表示测试数据个数。
对于每个测试数据,第一行整数 ,表示正方形的边长。第二行以一个整数 mm 开头( ),接下来 个不相同的整数,范围在 之间,以空格分开。
输出格式
对每个测试数据,输出一个整数表示最少需要移除的火柴棒个数。
样例
样例输入
2
2
0
3
3 12 17 23
样例输出
3
3
样例解释
比如
的时候最少需要移除
根火柴棒。
右上图初始状态去掉
火柴棒的
的正方形最少也需要移除
根火柴棒。
分析
的方格中一共有
根火柴棒,正方形数量为
的时候有
个。
直接枚举所有
种移除火柴棒的方案显然不行,下面我们考虑如何剪枝。
在DFS的过程中:
●如果移除当前火柴棒后,不能破坏任何正方形,那就没必要移除。
●如果把剩下的火柴棒全部移除,也会留下某些正方形,那后面情况就不用考虑了。
第一种剪枝意味着,我们需要预处理每条边是否属于某个正方形。
对于第二种剪枝,如果我们按火柴棒编号从小到大的顺序搜索,那么保存一下每个正方形最大编号的火柴棒,当搜索这个火柴棒时,如果正方形还存在就必须移除。
实现&讲解
本题读入数据后的预处理还是有点麻烦的,需要细心计算每个火柴棒和正方形的对应情况。
首先定义如下变量:
bool belong[N][N]; // belong[i][j] 表示火柴棒i是否属于正方形j
int mmax[N];// mmax[i] 表示正方形i最大编号的火柴棒
int m,ct;// m为总的火柴棒数里,ct为初始正方形数量
int ans;//最终答案
bool vis [N];//火柴棒初始状态true 表示不存在
然后枚举正方形的边长,左上角的位置,来找当前存在的正方形和火柴棒的对应情况。正方形编号从0开始,火柴棒编号从1开始。.
for(int sZ=1;sZ<=n;sZ++){//正方形边长
for (int i=0;i+sz-1< n; i++){//左上角的行数
for (int j=0;j+sz-1<n;j++){//左上角的列数
int x0=i*(n+n+1)+1+j; //上边
int x1 = x0+ sz*(n+n+1);//下边
int x2=x0+n;//左边
int x3=x2+sz;//右边
int ok=1;
for(int k=0;k<sz;k++){
if(vis[x0 + k] || vis[x1 + k] || vis[x2+(n+n+1)*k] || vis[x3+(n+n+1)*k]) {
ok=0;
break;
}
}
if (ok) {
mmax[ct] = x1 + sz-1;
for(int k=0;k<sz;k++){
belong[x0 + k][ct] = true;
belong[x1 + k][ct] = true;
belong[x2 + (n + n + 1) *k][ct] = true;
belong[x3 + (n + n + 1) *k][ct] = true;
}
ct++;
}
}
}
}
容易写出下面的DFS
函数,按照移除当前火柴棒的影响分三种情况讨论,其中已经包含了上述两种剪枝。
这里我们把正方形是否完整的集合用一个整数表示,因为最多有
个正方形,可以用long long
保存状态。
调用的时候写dfs(1, 0, (1LL << ct) - 1);
//u为当前火柴棒编号,sum为当前移除的火柴棒数里,state为当前完整正方形的集合
void dfs(int u,int sum,1ong 1ong state){
if(u==m+1){
if(state==0){
ans=min(ans,sum);
}
return;
}
//初始火柴棒不存在,直接搜下一个
if(vis[u]){
dfs(u+1,sum,state);
return;
}
int flag=0;//0不用移除1可以移除2必须移除
for(int i=0;i<ct;1++){
if((state&(1<<i)) && belong[u][i]){
if(mmax[i]==u){
flag=2;
}elseif(flag=0){
flag=1;
}
}
}
if(f1ag==0){
dfs(u+1,sum,state);
}else if(flag==2){
for(int i=0;i<ct;i++){
if((state>>i&1)&belong[u][1]){
state^=1LL<<i;
}
}
dfs(u+1,sum+1,state);
}else{
dfs(u+1,sum,state);
for(int i=0;i<ct;i++){
if((state>>i&1)&belong[u][i]){
state^=1LL<<i;
}
}
dfs(u+1,sum+1,state);
}
}
现在已经通过大部分情况了,但当
时,仍会超时,还差一个强力剪枝。
容易想到当前移除的总火柴棒数量超过ans
时,就没必要搜下去了,在dfs
函数开头加上
if(sum>=ans){
return;
}
准确来讲,应该是
实际剩下最少移除的火柴棒数量
, 就没必要搜了。
这个实际数量就是求剩余火柴棒中没有公共边的正方形的最大数量,这是一个最大独立集问题,本身就需要耗费很多时间。
但是我们可以设计一个函数,估算剩下最少移除的火柴棒数量,只要估算数量小于等于实际数量,剪枝就不会对结果的正确性造成影响,可以理解成没剪干净但是相当高效。也就是权衡剪枝本身的计算,和剪枝带来的优化,设计这么一个估计函数。