「UVA1603」破坏正方形 Square Destroyer - 题解

  • 前言

    一道 DLX 好题(即 Dancing Links X ),适合刚学来练手。

    用 DLX 就比较好想,也不会很难写(((


  • 分析

    首先,如何想到 DLX ?

    一个正方形有 \(4\) 条边(每条边若干火柴),选择一根火柴就可以破坏掉正方形。但是也可以选择多根。

    要保证每个正方形内部至少有一根火柴被选择,且要求选择火柴数量最少

    再看看重复覆盖问题:

    给定一个 \(N\)\(M\) 列的矩阵,矩阵中每个元素要么是 \(1\) ,要么是 \(0\)

    你需要在矩阵中选择若干行,使每一列都至少包含一个 \(1\) ,并且要求选择的行数最少。

    很明显,这是一道重复覆盖问题。

    接下来就是如何构建了。

    首先,把这些火柴抽象成 \(2n(n+1)\) 个点。

    然后每根火柴和所在边长为 \(len (1 \le len \le n)\) 的矩形的边上所有火柴连起来。

    如下图:(举例点 \(1\)


  • 代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector> 
#include<algorithm>
using namespace std;
const int Maxn=3600+5;
int T,n,m,l[Maxn],r[Maxn],u[Maxn],d[Maxn],s[Maxn];
int idx,tot,ans[Maxn],col[Maxn],row[Maxn];
bool st[Maxn]; // 1.作为估价函数 2.作为建图 
vector<int> sq[60];
void init()
{	for(int i=0;i<=m;i++)
	{	l[i]=i-1;r[i]=i+1;
		u[i]=d[i]=col[i]=i;
		s[i]=0;
	}
	l[0]=m;r[m]=0;
	idx=m+1;
}
int h() //估价函数 
{	int cnt=0;
	memset(st,0,sizeof(st));
	for(int i=r[0];i;i=r[i])
	{	if(st[col[i]])continue;
		cnt++;
		st[col[i]]=1;
		for(int j=d[i];j!=i;j=d[j])
			for(int k=r[j];k!=j;k=r[k])
				st[col[k]]=1;
	}
	return cnt;
} 
void add(int &hd,int &tl,int x,int y)
{	row[idx]=x;col[idx]=y;s[y]++;
	u[idx]=y;d[idx]=d[y];u[d[y]]=idx;d[y]=idx;
	r[hd]=l[tl]=idx;r[idx]=tl;l[idx]=hd;
	tl=idx++;
}
void remove(int p)
{	for(int i=d[p];i!=p;i=d[i])
		r[l[i]]=r[i],l[r[i]]=l[i];
}
void resum(int p)
{	for(int i=u[p];i!=p;i=u[i])
		r[l[i]]=i,l[r[i]]=i;
}
bool dfs(int k)
{	if(k+h()-1>tot)return 0;
	if(!r[0])return 1;
	int p=r[0];
	for(int i=r[0];i;i=r[i])
		if(s[p]>s[i])p=i;
	for(int i=d[p];i!=p;i=d[i])
	{	ans[k]=row[i];
		remove(i);
		for(int j=r[i];j!=i;j=r[j])remove(j);
		if(dfs(k+1))return 1;
		for(int j=l[i];j!=i;j=l[j])resum(j);
		resum(i);
	}
	return 0;
}
int main()
{	scanf("%d",&T);
	while(T--)
	{	int k;
		scanf("%d%d",&n,&k);
		m=idx=0;
		memset(st,0,sizeof(st));
		memset(col,0,sizeof(col));
		for(int i=1;i<=k;i++)
		{	int x;
			scanf("%d",&x);
			st[x]=1;
		}
		for(int len=1;len<=n;len++)
			for(int x=1;x+len-1<=n;x++)
				for(int y=1;y+len-1<=n;y++)
				{	m++;
					sq[m].clear();
					int dd=n*2+1;//一行过去(横n加上竖n+1)个数
					for(int i=0;i<len;i++) //向一个矩形的 4 边(上下左右)连去 
					{	sq[m].push_back((x-1)*dd+y+i); //上 
						sq[m].push_back((x-1)*dd+y+i+dd*len); //下 
						sq[m].push_back((x-1)*dd+y+n+i*dd); //左 
						sq[m].push_back((x-1)*dd+y+n+i*dd+len); //右 
					}
					for(int i=0;i<sq[m].size();i++) //相连的边有一个断了,直接删除所有即可 
						if(st[sq[m][i]]){m--;break;}
				}
		init();
		for(int i=1;i<=n*(n+1)*2;i++) //全部边
			if(!st[i])
			{	int hh=idx,tl=idx;
				for(int j=1;j<=m;j++)
					if(find(sq[j].begin(),sq[j].end(),i)!=sq[j].end()) //连边存在 
						add(hh,tl,i,j);
			} 
		tot=0;
		while(!dfs(1))tot++;
		printf("%d\n",tot);
	}
	return 0;
}

\[\text{by Rainy7} \]

posted @ 2021-02-27 22:10  Rainy7  阅读(101)  评论(0编辑  收藏  举报