您的位置:首页 > 其它

ACM练级日志:POJ 3740 与Dancing Links

2014-08-09 22:23 435 查看
感觉自己搜索能力还是不够,前些日子记得有个地方出过个题,不会做,一查解题报告发现跟个叫什么Dancing Links的玩意有关,于是就去查了查,然后发现这玩意似乎有点碉……刚刚AC掉模板题,做个记录…

Dancing Links,简称DLX,用于给矩阵精确匹配问题的DFS过程大幅度剪枝,甚至具有自动剪枝的作用。所谓矩阵精确匹配问题,就是你有一个矩阵,里面元素不是0就是1,然后希望你选出一些行,使得这些行的总集中,每一列都有且只有一个1. 原来朴素的DFS就是枚举列,然后把这一列删掉——问题就出在这里,“删掉”一列需要很高的时间复杂度,所以往往我们会只把它标记成“删除了”,但是接下来的搜索中,明明矩阵应该越来越小,我们却花着最初时的代价搜索矩阵,明显很亏。

DLX厉害的地方在于它的数据结构:它用一个十字链表结点来表示每一个矩阵中为1的地方,然后它利用双向链表方便的删除以及删除恢复操作:删除x时只需 R[ L[x] ]= R[x] , L[ R[x] ] = L[x] (我的左边的右边是我的右边,反之亦然), x就找不着了,然后恢复的时候,只需要两句:L[ R[x] ] = x, R[ L[x] ] =x 就又能找着x了。这个对于回溯是非常有用的。

而且DLX解决问题的单一性使它可以作为模板直接使用,前期建模以后,直接构建矩阵然后去DFS就搞定了……所以最麻烦的地方恐怕还是建模。

不管怎样,先放一个刚刚弄的模板吧,只过了POJ 3740, 不知道好不好使。另外momodi的关于DLX的文章非常好懂,代码也很规范,值得一读~

#include<stdio.h>
#include<string.h>
#include<math.h>
using namespace std;

const int INF=2<<25;
const int MAXNUM=6010;

int n,m;
int u[MAXNUM], d[MAXNUM], l[MAXNUM], r[MAXNUM];//上下左右
int s[MAXNUM], col[MAXNUM];//s[i]:第i列有几个节点,即有几个1,col[i]能告诉你i这个节点在第几列

int head;//总表头,其实就是0号节点
int p_nodes;//目前用了多少节点

void del(int c)//删掉列c以及列c中A[i][j]=1的所有行
{
l[ r[c] ] = l[c];
r[ l[c] ] = r[c];

for(int i=d[c]; i!=c; i=d[i])
{
for(int j=r[i]; j!=i; j=r[j])
{
u[ d[j] ] = u[j];
d[ u[j] ] = d[j];
s[ col[j] ] --;
}
}
return;
}

void resume(int c)//恢复上面的操作
{
for(int i=u[c]; i!=c; i=u[i])
{
for(int j=l[i]; j!=i; j=l[j])
{
s[ col[j] ] ++;
d[ u[j] ]=j;
u[ d[j] ]=j;
}
}

r[ l[c] ] =c;
l[ r[c] ] = c;
return;
}

bool DFS(int depth)
{
////TEST
//cout<<depth<<endl;

if(r[head] == head)
return true;//矩阵被删干净了

int min1=INF, now;//挑一个1数最少列的先删
for(int t=r[head]; t!=head; t=r[t])
{
if(s[t] <=min1 )
{
min1=s[t];
now=t;
}
}

del(now);//删掉此列
int i, j;
for(i=d[now]; i!=now; i=d[i])
{//枚举这一列每个1由哪行来贡献,这行即为暂时性的答案,如果需记录答案,此时ans[depth]=i
for(j=r[i]; j!=i; j=r[j])
{
del( col[j] );//选取了第i行,就要把本行所有的1所在列都删掉
}

bool tmp=DFS(depth+1);

for(j=l[i]; j!=i; j=l[j])
{
resume(col[j]);
}

if(tmp==true)
return true;

}
resume(now);
return false;
}

void init()
{
memset(u,0,sizeof(u));
memset(d,0,sizeof(d));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
memset(s,0,sizeof(s));
memset(col,0,sizeof(col));

head=0;
p_nodes=0;
}

void InitLinks()//注意:该链表两个方向都是循环的,最上面的上面指最下面
{
int i,j;
r[head]=1;
l[head]=m;

for(i=1;i<=m;i++)//制作列的表头,使用结点1~m
{
l[i]=i-1;
r[i]=i+1;
if(i==m)
r[i]=head;
u[i]=i;
d[i]=i;
s[i]=0;
col[i]=i;
}

p_nodes=m;
for(i=1;i<=n;i++)
{
int row_first1=-1;//看看这一行是不是已经有第一个1了
for(j=1;j<=m;j++)
{
int k;
scanf("%d", &k);
if(k==1)
{
p_nodes++;
s[j] ++;
u[p_nodes] = u[j];
d[ u[j] ] = p_nodes;
u[j] = p_nodes;
d[p_nodes] = j;

col[p_nodes] = j;//和列的关系处理完毕

if(row_first1==-1)
{
l[p_nodes] = r[p_nodes] = p_nodes;
row_first1=p_nodes;
}

else
{
l[p_nodes] = l[row_first1];
r[ l[row_first1] ] = p_nodes;
r[p_nodes] = row_first1;
l[ row_first1 ]=p_nodes;//和行的关系处理完毕
}
}
}
}
}

int main()
{
while(scanf("%d %d", &n, &m) ==2)
{
init();
InitLinks();
bool ok=true;
for(int i=1;i<=m;i++)
{
if(s[i]==0)
{
ok=false;
break;
}
}
if(ok)
ok=DFS(1);

if(ok)
printf("Yes, I found it\n");
else
printf("It is impossible\n");
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: