「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}
\]