ACM練級日誌:POJ 3740 與Dancing Links

感覺自己搜索能力還是不夠,前些日子記得有個地方出過個題,不會做,一查解題報告發現跟個叫什麼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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章