Dancing Links X 学习笔记

X 算法

用于解决精确覆盖问题,即给定一个零一矩阵,选出若干行让每一行恰有一个一。

\[\begin{bmatrix}0&0&1&0&1&1&0\\1&0&0&1&0&0&1\\0&1&1&0&0&1&0\\1&0&0&1&0&0&0\\0&1&0&0&0&0&1\\0&0&0&1&1&0&1\end{bmatrix} \]

这是一个矩阵。

先随便选择一行删掉,假设选择了第一行。那么第三、六、七列可以删掉,因为这些列上已经有数;同时包含第三、六、七列的行,也就是第三、六行也要删掉。那么矩阵就变成了这样:

\[\begin{bmatrix}1&0&1&1\\1&0&1&0\\0&1&0&1\end{bmatrix} \]

用相同的方法求解这个子问题。

此时假如删掉第一行,会导致行被删完,但第二列没有被删,无解。所以当矩阵为空时,要求最后被删的行全为一。

Dancing Links X

X 算法中有大量删除行、列的操作。可以用循环链表维护矩阵中的一。

每个一都有四个指针指向上下左右的一。每一行,每一列都有一个头指针,特别地,列的头指针是虚点 \(0\sim m\)

build

初始化列的头指针。

void build(int b){
  cnt=b;
  for(int i=0;i<=b;i++)l[i]=i?i-1:b,r[i]=i==b?0:i+1,u[i]=d[i]=i;
}

insert

插入时可以直接把这个点放在行和列的头指针后面,实际上链表的相对顺序是不重要的,只要能遍历所在行和列就行了。

void insert(int a,int b){
  sz[b]++,cnt++,x[cnt]=a,y[cnt]=b,u[cnt]=b,d[cnt]=d[b],u[d[cnt]]=d[u[cnt]]=cnt;
  if(!head[a])head[a]=l[cnt]=r[cnt]=cnt;
  else l[cnt]=head[a],r[cnt]=r[head[a]],l[r[cnt]]=r[l[cnt]]=cnt;
}

remove 和 recover

表示删除和恢复一列及相关的行。其中还要维护每列一的个数,在之后会用到。

void remove(int a){
  l[r[a]]=l[a],r[l[a]]=r[a];
  for(int i=d[a];i!=a;i=d[i])for(int j=r[i];j!=i;j=r[j])u[d[j]]=u[j],d[u[j]]=d[j],sz[y[j]]--;
}
void recover(int a){
  for(int i=u[a];i!=a;i=u[i])for(int j=l[i];j!=i;j=l[j])u[d[j]]=d[u[j]]=j,sz[y[j]]++;
  l[r[a]]=a,r[l[a]]=a;
}

dance

这里找到一的个数最少的一列,枚举这一列相关的行删去。这样做可以搜索到所有情况(因为每次都会删一个一),而且搜索树最小。

bool dance(){
  int pos=r[0];
  if(!pos)return 1;
  for(int i=r[0];i;i=r[i])if(sz[i]<sz[pos])pos=i;
  remove(pos),num++;
  for(int i=d[pos];i!=pos;i=d[i]){
    ans[num]=x[i];
    for(int j=r[i];j!=i;j=r[j])remove(y[j]);
    if(dance())return 1;
    for(int j=l[i];j!=i;j=l[j])recover(y[j]);
  }
  return recover(pos),num--,0;
}

重复覆盖问题

DLX 还可以解决重复覆盖问题。

UVA1603

首先可以将每个正方形看作列,每条边看做行。问题变成选择最少的行使每列有至少一个一。

那么删除列时就不用删相关行,因为一列可以有多个一。

另外,由于要求最少,可以用 IDA*。估价函数可以这么设计:枚举每一列,选取这一列的相关行,但是只花费一行的代价。

证明:选取了所有相关行后,如果枚举到的列没有被覆盖,那么这个列会作为一个覆盖的第一个一,此时这一行的代价是无法避免的。

#include<bits/stdc++.h>
using namespace std;
int t,n,m,lim,head[65],sz[3605],l[3605],r[3605],u[3605],d[3605],cnt,x[3605],y[3605],num,ans[65];
bool vis[65];
vector<int>e[65];
void build(int b){
  cnt=b;
  for(int i=0;i<=b;i++)l[i]=i?i-1:b,r[i]=i==b?0:i+1,u[i]=d[i]=i;
}
int h(){
  int cnt=0;
  memset(vis,0,sizeof(vis));
  for(int i=r[0];i;i=r[i]){
    if(vis[i])continue;
    cnt++,vis[i]=1;
    for(int j=d[i];j!=i;j=d[j])for(int k=r[j];k!=j;k=r[k])vis[y[k]]=1;
  }
  return cnt;
}
void insert(int a,int b){
  sz[b]++,cnt++,x[cnt]=a,y[cnt]=b,u[cnt]=b,d[cnt]=d[b],u[d[cnt]]=d[u[cnt]]=cnt;
  if(!head[a])head[a]=l[cnt]=r[cnt]=cnt;
  else l[cnt]=head[a],r[cnt]=r[head[a]],l[r[cnt]]=r[l[cnt]]=cnt;
}
void remove(int a){
  for(int i=d[a];i!=a;i=d[i])l[r[i]]=l[i],r[l[i]]=r[i];
}
void recover(int a){
  for(int i=u[a];i!=a;i=u[i])l[r[i]]=r[l[i]]=i;
}
bool dance(int dep){
  if(dep+h()>lim)return 0;
  int pos=r[0];
  if(!pos)return 1;
  for(int i=r[0];i;i=r[i])if(sz[i]<sz[pos])pos=i;
  for(int i=d[pos];i!=pos;i=d[i]){
    ans[dep]=x[i],remove(i);
    for(int j=r[i];j!=i;j=r[j])remove(j);
    if(dance(dep+1))return 1;
    for(int j=l[i];j!=i;j=l[j])recover(j);
    recover(i);
  }
  return 0;
}
int main(){
  cin>>t;
  while(t--){
    cin>>n>>m;
    for(int i=1,x;i<=m;i++)cin>>x,vis[x]=1;
    m=0;
    for(int i=1;i<=n;i++){
      for(int j=1;j<=n-i+1;j++){
        for(int k=1;k<=n-i+1;k++){
          e[++m].clear();
          for(int l=0;l<i;l++){
            e[m].push_back((j-1)*(2*n+1)+k+l);
            e[m].push_back((j-1)*(2*n+1)+k+l+(2*n+1)*i);
            e[m].push_back((j-1)*(2*n+1)+k+n+l*(2*n+1));
            e[m].push_back((j-1)*(2*n+1)+k+n+l*(2*n+1)+i);
          }
          for(int l=0;l<e[m].size();l++){
            if(vis[e[m][l]]){
              m--;
              break;
            }
          }
        }
      }
    }
    build(m);
    for(int i=1;i<=m;i++)for(int j=0;j<e[i].size();j++)insert(e[i][j],i);
    for(lim=0;!dance(0);lim++);
    cout<<lim<<'\n',cnt=num=0,memset(head,0,sizeof(head)),memset(size,0,sizeof(size)),memset(l,0,sizeof(l)),memset(r,0,sizeof(r)),memset(u,0,sizeof(u)),memset(d,0,sizeof(d));
  }
  return 0;
}

例题

P1784

转化一下,相当于每行、每列、每宫中有且仅有一个 \(1\sim 9\),且每格中有且仅有一个数字,共 \(324\) 列;每格有九种数字,共 \(729\) 行,\(3241\) 个一。对于已经有的数,就只保留符合初始状态的一行。

build(324);
for(int i=0;i<9;i++){
  for(int j=0,t;j<9;j++){
    cin>>t;
    for(int k=1;k<=9;k++){
      if(t&&t!=k)continue;
      insert(i*9*9+j*9+k,i*9+j+1);
      insert(i*9*9+j*9+k,81+i*9+k);
      insert(i*9*9+j*9+k,81*2+j*9+k);
      insert(i*9*9+j*9+k,81*3+(i/3*3+j/3)*9+k);
    }
  }
}
dance();
for(int i=1;i<=num;i++)a[(ans[i]-1)/81][(ans[i]-1)/9%9]=ans[i]%9?ans[i]%9:9;
for(int i=0;i<9;i++)for(int j=0;j<9;j++)cout<<a[i][j]<<(j==8?'\n':' ');

类似的有 SP1110UVA1309

用 DLX 枚举本身就起到了剪枝的作用,可以做 P1074

P4205

每格有且仅有一个珠子,每个零件恰好用了一次,共 \(67\) 列;每个零件有不同的位置,再加上旋转翻转,共 \(2730\) 行,\(15084\) 个一。

枚举零件时,可以打表出零件的珠子与其中一个珠子坐标的差,然后枚举这个增量的正负号,枚举是否交换横纵坐标的增量。

const int len[12]={3,4,4,4,5,5,5,5,5,5,5,5},p[12][5][2]={{{0,0},{1,0},{0,1}},{{0,0},{0,1},{0,2},{0,3}},{{0,0},{1,0},{0,1},{0,2}},{{0,0},{1,0},{0,1},{1,1}},{{0,0},{1,0},{2,0},{2,1},{2,2}},{{0,0},{0,1},{1,1},{0,2},{0,3}},{{0,0},{1,0},{0,1},{0,2},{1,2}},{{0,0},{1,0},{0,1},{1,1},{0,2}},{{0,0},{0,1},{0,2},{1,2},{1,3}},{{0,0},{-1,1},{0,1},{1,1},{0,2}},{{0,0},{1,0},{1,1},{2,1},{2,2}},{{0,0},{1,0},{0,1},{0,2},{0,3}},};
int main(){
  build(67);
  for(int i=1;i<=10;i++)cin>>s[i]+1;
  for(int i=1;i<=10;i++)for(int j=1;j<=i;j++)if(s[i][j]!='.')vis[s[i][j]-'A']=1;
  for(int t=0;t<12;t++){
    for(int f=0;f<=1;f++){
      for(int dx=-1;dx<=1;dx+=2){
        for(int dy=-1;dy<=1;dy+=2){
          for(int x=1;x<=10;x++){
            for(int y=1;y<=x;y++){
              for(int i=0;i<len[t];i++){
                int nx=x+dx*p[t][i][f],ny=y+dy*p[t][i][!f];
                if(nx<1||nx>10||ny<1||ny>nx||(vis[t]&&s[nx][ny]!=t+'A'))goto tag;
              }
              a[++n]=(node){t,f,dx,dy,x,y};
              for(int i=0;i<len[t];i++){
                int nx=x+dx*p[t][i][f],ny=y+dy*p[t][i][!f];
                insert(n,nx*(nx-1)/2+ny);
              }
              insert(n,56+t);
              tag:;
            }
          }
        }
      }
    }
  }
  if(dance()){
    for(int i=1;i<=num;i++){
      int t=a[ans[i]].t,f=a[ans[i]].f,dx=a[ans[i]].dx,dy=a[ans[i]].dy,x=a[ans[i]].x,y=a[ans[i]].y;
      for(int i=0;i<len[t];i++){
        int nx=x+dx*p[t][i][f],ny=y+dy*p[t][i][!f];
        temp[nx][ny]=(char)(t+'A');
      }
    }
    for(int i=1;i<=10;i++,putchar('\n'))for(int j=1;j<=i;j++)cout<<temp[i][j];
  }
  else puts("No solution");
}

[[搜索]]

posted @ 2024-03-01 09:42  lgh_2009  阅读(3)  评论(0编辑  收藏  举报