【题解】火柴棒

题目

题目描述

有一个由火柴棒作为边组成的 N × N N×N 的格子。( N 5 N\le 5 )按照下图,给火柴棒编号。(这是 N = 3 N=3 的情况,其它情况类似)
在这里插入图片描述
现在将移除某些火柴棒的状态作为初始状态,需要再移除一些火柴棒,以保证图中一个正方形也没有。请求出所有需要移除火柴棒的最少根数。

输入格式

第一行一个整数 T T 表示测试数据个数。

对于每个测试数据,第一行整数 N N ,表示正方形的边长。第二行以一个整数 mm 开头( m 2 n ( n + 1 ) m\le 2n(n+1) ),接下来 m m 个不相同的整数,范围在 [ 1 , 2 n ( n + 1 ) ] [ 1 , 2 n ( n + 1 ) ] [1,2n(n+1)][1,2n(n+1)] 之间,以空格分开。

输出格式

对每个测试数据,输出一个整数表示最少需要移除的火柴棒个数。

样例

样例输入

2
2
0
3
3 12 17 23

样例输出

3
3

样例解释

比如 2 × 2 2\times2 的时候最少需要移除 3 3 根火柴棒。
右上图初始状态去掉 12 12 17 17 23 23 火柴棒的 3 × 3 3\times3 的正方形最少也需要移除 3 3 根火柴棒。


分析

N × N N \times N 的方格中一共有 2 N ( N + 1 ) 2N(N+1) 根火柴棒,正方形数量为 i = 1 n i 2 \sum_{i=1}^n i^2 的时候有 55 55 个。
直接枚举所有 2 2 N ( N + 1 ) 2^{2N(N+1)} 种移除火柴棒的方案显然不行,下面我们考虑如何剪枝。
在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函数,按照移除当前火柴棒的影响分三种情况讨论,其中已经包含了上述两种剪枝。
这里我们把正方形是否完整的集合用一个整数表示,因为最多有 55 55 个正方形,可以用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);
	}
}

现在已经通过大部分情况了,但当 N = 5 N = 5 时,仍会超时,还差一个强力剪枝。
容易想到当前移除的总火柴棒数量超过ans时,就没必要搜下去了,在dfs函数开头加上

if(sum>=ans){
	return;
}

准确来讲,应该是 s u m + sum+ 实际剩下最少移除的火柴棒数量 a n s \geq ans , 就没必要搜了。
这个实际数量就是求剩余火柴棒中没有公共边的正方形的最大数量,这是一个最大独立集问题,本身就需要耗费很多时间。
但是我们可以设计一个函数,估算剩下最少移除的火柴棒数量,只要估算数量小于等于实际数量,剪枝就不会对结果的正确性造成影响,可以理解成没剪干净但是相当高效。也就是权衡剪枝本身的计算,和剪枝带来的优化,设计这么一个估计函数。

猜你喜欢

转载自blog.csdn.net/tanfuwen_/article/details/106860381