DLX演算法一覽
目錄:
1 X思想的瞭解。
- 連結串列的遞迴與回溯。
- 具體操作。
- 優化。
- 一些應用與應用中的再次優化(例題)。
- 練手題
X思想的瞭解。
首先了解DLX是什麼?
DLX是一種多元未飽和型指令集結構,DLX 代表中級車、加長軸距版本、內飾改款、尊貴車豪華版車型。---百科百度
不不不,我不講這些明明就是不懂
DLX是什麼,一種解決精準覆蓋問題的做法,一般不叫演算法,下面講。
模版題:
時間限制:10000ms
單點時限:1000ms
記憶體限制:256MB
描述
小Ho最近遇到一個難題,他需要破解一個棋局。
棋局分成了n行,m列,每行有若干個棋子。小Ho需要從中選擇若干行使得每一列有且恰好只有一個棋子。
比如下面這樣局面:
其中1表示放置有棋子的格子,0表示沒有放置棋子。
對於上面這個問題,小Ho經過多次嘗試以後得到了解為選擇2、3、4行就可以做到。
但是小Ho覺得自己的方法不是太好,於是他求助於小Hi。
小Hi:小Ho你是怎麼做的呢?
小Ho:我想每一行都只有兩種狀態,選中和未被選中。那麼我將選中視為1,未選中視為0。則每一種組合恰好對應了一個4位的01串,也就是一個4位的二進位制數。
小Hi:恩,沒錯。
小Ho:然後我所做的就是去列舉每一個二進位制數然後再來判定是否滿足條件。
小Hi:小Ho你這個做法本身沒什麼問題,但是對於棋盤行數再多一點的情況就不行了。
小Ho:恩,我也這麼覺得,那你有什麼好方法麼?
小Hi:我當然有了,你聽我慢慢道來。
提示:跳舞鏈
輸入
第1行:1個正整數t,表示資料組數,1≤t≤10。
接下來t組資料,每組的格式為:
第1行:2個正整數n,m,表示輸入資料的行數和列數。2≤n,m≤100。
第2..n+1行:每行m個數,只會出現0或1。
輸出
第1..t行:第i行表示第i組資料是否存在解,若存在輸出"Yes",否則輸出"No"。
樣例輸入
2
4 4
1 1 0 1
0 1 1 0
1 0 0 0
0 1 0 1
4 4
1 0 1 0
0 1 0 0
1 0 0 0
0 0 1 1
樣例輸出
No
Yes
DL=Dancing Link跳舞鏈(雙向十字連結串列),一種資料結構,用來優化X演算法的,所以叫DLX,所以在嚴格意義上來講,DLX就是一個優美的暴力可人家就是快,就是牛逼呀!
連結串列大家都知道,如果這都不知道,這篇文章你多半看不懂的!
雙向十字連結串列是什麼?
一個圖祝大家秒記:
沒錯,雙向十字連結串列有四個方向的鏈,而普通的雙向連結串列只有兩個方向的鏈。所以他更牛逼。
那麼刪除就更原來一樣呀!
那麼,X思想是什麼。我是不是想Y了。。。
對於一個矩陣:
對於這種圖,我們先找到第一個沒有覆蓋的列:
然後依次找一個這一列為1的行,然後將這一列與這一行標為紫色。
霸王硬上弓,刪掉!
不對,刪掉這一行還會有一行被覆蓋。
Look,這一行的橙色部分也被覆蓋,因此橙色這一列也應該被刪掉。
於是,我們應當把第三行刪掉(藍色部分)。
這樣把所有被顏色圈住的格子刪掉,同時將第一行丟入ans陣列。
醜得一批。。。
那麼,照舊,選擇第一個沒有被覆蓋的列,第二列,同時我們選擇第二行作為ans。
刪除之後,我們繼續找,發現第三列還沒被覆蓋,但是這一列沒有一行有1了(完全連行都沒有了。。。失敗?)
不存在的,回溯大法好呀!
用填色的部分就是刪除的部分(先將第一列刪除,再將覆蓋第一列的第一行與第二行刪除,同時,我們選擇了第二行,所以我們將第2、5行刪除),同時我們將ans[1]=2。
這麼一刪,我們把第1、2、5列給刪了,同時第1、2行也被刪了,重複以下步驟,我們發現只要選擇2、3行,就木有問題了。
但是,回溯過程代價打得一批這不是你畫圖醜得一批的理由!!!
連結串列的遞迴與回溯。
我們發現用連結串列不僅刪除十分快,而且回溯也十分迅猛,在空間與時間上都十分優秀!
我們只需要用連結串列儲存1的位置,同時,把每一列的編號也作為一個節點就可以了,然後用雙向十字連結串列建個圖,然後進行那些步驟(每次跳0號節點的右邊,選列的話只需要跳選的節點的下面或上面,雙向連結串列是迴圈的)。
圖中第一行中0、1、2、3、4、5代表列數,而下面的1代表這個節點的權值是1。
其實連結串列還有個重要的性質:
平常連結串列刪除只是讓左邊的指向自己右邊,同時右邊又指向自己左邊,但是自己的左邊和右邊還是指向他們的,如果要恢復的話,只需要讓左邊和右邊的人再次指向自己就好了,真是方便。
不過雙向十字連結串列要注意上下左右都要刪除與他的聯絡。
所以為什麼叫跳舞鏈,我怎麼知道?
具體實現
定義程式碼:
#include<cstdio>
#include<cstring>
using namespace std;
int a[2100];//新增時記錄第i個1所在的列數
struct node
{
int l,r,u,d,lie,hang;//l代表左,r代表右,u代表上,d代表下,lie代表lie標記,為優化做準備,而hang則是hang座標,經常能有許多有用的資訊。
};
struct DLX
{
node p[610000];int len;//p代表連結串列,len代表節點數
int size[2100],last[2100];//size代表第i列有多少節點,而last陣列記錄第i列的最後一個節點編號
inline void make(int l,int r,int u,int d,int lie,int hang){p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].lie=lie;p[len].hang=hang;}//製造函式
inline void sxdel(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}//上下連結串列刪除
inline void nsxdel(int x){p[p[x].u].d=p[p[x].d].u=x;}//上下連結串列還原
inline void zydel(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}//左右連結串列刪除
inline void nzydel(int x){p[p[x].l].r=p[p[x].r].l=x;}//左右連結串列還原
}dlx;
初始化:
inline void clear(int x)//初始化x列
{
len=0;p[0].l=p[0].r=p[0].u=p[0].d=0;//將0號節點初始化
for(int i=1;i<=x;i++)//建x列
{
size[i]=0;last[i]=i;//重置size與last
len++;make(i-1,p[i-1].r,i,i,i,0);//make製造第i列節點
nzydel(i);//將左右的人指向自己
}
}
將第row行插入到連結串列裡,插入的節點數為a[0]。
inline void add(int row)//新增第row行
{
if(a[0]==0)return ;//其實不加也可以,即使下面make了一個沒用的節點,但是並不會訪問到它
len++;make(len,len,last[a[1]],p[last[a[1]]].d,a[1],row);
nsxdel(len);size[a[1]]++;last[a[1]]=len;//製造本行第一個節點
for(int i=2;i<=a[0];i++)//遍歷
{
len++;make(len-1,p[len-1].r,last[a[i]],p[last[a[i]]].d,a[i],row);//製造第i個節點
nsxdel(len);nzydel(len);size[a[i]]++;last[a[i]]=len;//讓上下左右的節點指向自己。
}
}
刪除(遞迴中的刪除):
//將第i列刪除,同時將相關的行也徹底刪除
inline void del(int x)//注意:x的行數為0
{
zydel(x);//先刪掉第x列與其他列的練習
for(int i=p[x].u;i!=x;i=p[i].u)//找到這一列為1的行
{
for(int j=p[i].l;j!=i;j=p[j].l)sxdel(j),size[p[j].lie]--;//將這一行刪掉。
}
}
回溯:
//這就不多講了,不過就反過來罷了
inline void back(int x)//x的行數為0
{
nzydel(x);
for(int i=p[x].u;i!=x;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)nsxdel(j),size[p[j].lie]++;
}
}
優化
還沒講重點吧!
我們發現,輸出答案的話,與一開始選擇的列數並沒有多大關係(原本是直接選擇p[0].r),所以我們可以選擇列數中節點最少的作為物件,減少遞迴次數!
//遞迴過程
int dance(int x)
{
if(!p[0].r)return x;//結束,返回
int first,mi=999999999;
for(int i=p[0].r;i;i=p[i].r)//找最少列
{
if(size[p[i].lie]<mi)mi=size[p[i].lie],first=i;
}
if(mi==0)return 0;//有一列沒有辦法覆蓋?返回0
del(first);//先刪除
for(int i=p[first].u;i!=first;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].lie);//將這一行能覆蓋的區域刪除
int tt=dance(x+1);//遞迴
if(tt)return tt;
for(int j=p[i].l;j!=i;j=p[j].l)back(p[j].lie);//回溯
}
back(first);
return 0;
}
這樣就完了?
其實這樣還很慢!
注意這裡:
for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].lie);//將這一行能覆蓋的區域刪除
int tt=dance(x+1);//遞迴
if(tt)return tt;
for(int j=p[i].l;j!=i;j=p[j].l)back(p[j].lie);//回溯
我們發現:
原本在刪除的時候,第一個列與第二個列中相交的行數在第一次刪除就沒了,但是在回溯的時候,第一個列與第二個列中相交的行數在第一次回溯又回來了,而第二列中又多遍歷了一遍,後面也是如此!
但是,這麼打就沒問題了:
for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].lie);//將這一行能覆蓋的區域刪除
int tt=dance(x+1);//遞迴
if(tt)return tt;
for(int j=p[i].r;j!=i;j=p[j].r)back(p[j].lie);//回溯
完整程式碼:
#include<cstdio>
#include<cstring>
using namespace std;
int a[2100];//新增時記錄第i個1所在的列數
struct node
{
int l,r,u,d,lie,hang;//l代表左,r代表右,u代表上,d代表下,lie代表lie標記,為優化做準備,而hang則是hang座標,經常能有許多有用的資訊。
};
struct DLX
{
node p[610000];int len;//p代表連結串列,len代表節點數
int size[2100],last[2100];//size代表第i列有多少節點,而last陣列記錄第i列的最後一個節點編號
inline void make(int l,int r,int u,int d,int lie,int hang){p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].lie=lie;p[len].hang=hang;}//製造函式
inline void sxdel(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}//上下連結串列刪除
inline void nsxdel(int x){p[p[x].u].d=p[p[x].d].u=x;}//上下連結串列還原
inline void zydel(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}//左右連結串列刪除
inline void nzydel(int x){p[p[x].l].r=p[p[x].r].l=x;}//左右連結串列還原
inline void clear(int x)//初始化x列
{
len=0;p[0].l=p[0].r=p[0].u=p[0].d=0;//將0號節點初始化
for(int i=1;i<=x;i++)//建x列
{
size[i]=0;last[i]=i;//重置size與last
len++;make(i-1,p[i-1].r,i,i,i,0);//make製造第i列節點
nzydel(i);//將左右的人指向自己
}
}
inline void add(int row)//新增第row行
{
if(a[0]==0)return ;//其實不加也可以,即使下面make了一個沒用的節點,但是並不會訪問到它
len++;make(len,len,last[a[1]],p[last[a[1]]].d,a[1],row);
nsxdel(len);size[a[1]]++;last[a[1]]=len;//製造本行第一個節點
for(int i=2;i<=a[0];i++)//遍歷
{
len++;make(len-1,p[len-1].r,last[a[i]],p[last[a[i]]].d,a[i],row);//製造第i個節點
nsxdel(len);nzydel(len);size[a[i]]++;last[a[i]]=len;//讓上下左右的節點指向自己。
}
}
//將第i列刪除,同時將相關的行也徹底刪除
inline void del(int x)//注意:x的行數為0
{
zydel(x);//先刪掉第x列與其他列的練習
for(int i=p[x].u;i!=x;i=p[i].u)//找到這一列為1的行
{
for(int j=p[i].l;j!=i;j=p[j].l)sxdel(j),size[p[j].lie]--;//將這一行刪掉。
}
}
//這就不多講了,不過就反過來罷了
inline void back(int x)//x的行數為0
{
nzydel(x);
for(int i=p[x].u;i!=x;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)nsxdel(j),size[p[j].lie]++;
}
}
//遞迴過程
int dance(int x)
{
if(!p[0].r)return x;//結束,返回
int first,mi=999999999;
for(int i=p[0].r;i;i=p[i].r)//找最少列
{
if(size[p[i].lie]<mi)mi=size[p[i].lie],first=i;
}
if(mi==0)return 0;//有一列沒有辦法覆蓋?返回0
del(first);//先刪除
for(int i=p[first].u;i!=first;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].lie);//將這一行能覆蓋的區域刪除
int tt=dance(x+1);//遞迴
if(tt)return tt;
for(int j=p[i].r;j!=i;j=p[j].r)back(p[j].lie);//回溯
}
back(first);
return 0;
}
}dlx;
int main()
{
int T;scanf("%d",&T);//多組資料
while(T--)
{
int n,m;scanf("%d%d",&n,&m);//輸入
dlx.clear(m);//初始化
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x;scanf("%d",&x);
if(x)a[++a[0]]=j;
}
dlx.add(i);
a[0]=0;//找到1的數量
}
if(dlx.dance(0))printf("Yes\n");//輸出
else printf("No\n");
}
return 0;
}
至此,普通DLX講完了。
一些應用與應用中的再次優化(例題)。
啊哈,DLX只能解決精準覆蓋問題?這麼雞肋?
不存在的!
其實,他還可以裝逼、成為神犇通過轉換模型等一系列方法做出一些要不斷優化暴力才可以做出來的毒瘤題!
先介紹第一種應用:
重複覆蓋問題
如題目所講,在精準覆蓋問題中,每一列只能被一個1覆蓋,在這裡可以被多個1覆蓋,但是仍然要求每一列都要被覆蓋,求最少用幾行可以達到要求。
至於例題,上網搜神龍的問題,我上不去,所以。。。
但是,有一個更不錯的例題哪裡更不錯了。。。
Radar
傳送門!
T組資料
有N個城市,M個雷達,每次最多用K個雷達。
給你他們的座標,判斷雷達半徑最少為多少才可以覆蓋所有的城市。
- 1 ≤ T ≤ 20
- 1 ≤ N, M ≤ 50
- 1 ≤ K ≤ M
- 0 ≤ X, Y ≤ 1000
先輸入N、M、K
然後給你N個城市的座標與M個雷達的座標(整數)
輸出最小半徑(保留6位小數)
1
3 3 2
3 4
3 1
5 4
1 1
2 2
3 3
2.236068
嗯,一道不錯的二分DLX練手題
首先,二分+重複覆蓋問題
刪除選擇的這一行與這一列
刪除與回溯:
inline void del(int x)//刪除第x列,x的行數不為0
{
for(int i=p[x].u;i!=x;i=p[i].u)zydel(i);
}
inline void back(int x)//回溯,x的行數不為0
{
for(int i=p[x].u;i!=x;i=p[i].u)nzydel(i);
}
嗯,然後我們就A了
真香。。。。
這就AC了?那麼刀片店就要倒閉了。。。
因為del函式的變動,整個矩陣密度下降特別慢,結果導致悲劇的發生。
這個時候,我們就要用類似A中的期望函數了!(為什麼不是IDA?沒有迭代加深)。
期望函式就是:如果將能夠覆蓋當前列的所有行全部選中,去掉這些行能夠覆蓋到的列,將這個操作作為一步操作。重複此操作直到所有列都被覆蓋時用了多少步,加上現在的步數,如果大於當前最小函式,就return ;
這樣的話,我們的重複覆蓋問題也可以快得猛如虎了!
至於做法,二分半徑,然後把城市當成列,然後雷達當成行,將雷達能覆蓋的城市設為1,然後DLX,判斷最小選的行數如果小於等於K,就合法。
上程式碼!
#include<cstdio>
#include<cstring>
#include<cmath>
#pragma GCC optimize(2)//不,你沒有看到這句話
using namespace std;
typedef long long ll;
const ll inf=1e8;
int a[100],ans=0;
struct node
{
int l,r,u,d,lie,hang;
};//雙向十字連結串列
struct DLX
{
//大多函式以前打過註釋,就不打了
node p[210000];int len;
int size[100],last[100];
inline void make(int l,int r,int u,int d,int lie,int hang){p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].lie=lie;p[len].hang=hang;}
inline void sxdel(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}
inline void nsxdel(int x){p[p[x].u].d=p[p[x].d].u=x;}
inline void zydel(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}
inline void nzydel(int x){p[p[x].l].r=p[p[x].r].l=x;}
inline void clear(int x)
{
p[0].l=p[0].r=p[0].u=p[0].d=0;len=0;
for(int i=1;i<=x;i++)
{
size[i]=0;last[i]=i;
len++;make(i-1,p[i-1].r,i,i,i,0);
nzydel(len);
}
}
inline void add(int row)
{
if(!a[0])return ;
len++;make(len,len,last[a[1]],p[last[a[1]]].d,a[1],row);
nsxdel(len);size[a[1]]++;last[a[1]]=len;
for(int i=2;i<=a[0];i++)
{
len++;make(len-1,p[len-1].r,last[a[i]],p[last[a[i]]].d,a[i],row);
nsxdel(len);nzydel(len);size[a[i]]++;last[a[i]]=len;
}
}
inline void del(int x)//刪除第x列,x的行數不為0
{
for(int i=p[x].u;i!=x;i=p[i].u)zydel(i);
}
inline void back(int x)//回溯,x的行數不為0
{
for(int i=p[x].u;i!=x;i=p[i].u)nzydel(i);
}
bool vis[100];
inline int ex()//A*期望函式
{
int ret=0;
for(int i=p[0].r;i;i=p[i].r)vis[i]=true;//初始化
for(int i=p[0].r;i;i=p[i].r)
{
if(vis[i])
{
ret++;
vis[i]=false;
for(int j=p[i].u;j!=i;j=p[j].u)
{
for(int kk=p[j].l;kk!=j;kk=p[kk].l)vis[p[kk].lie]=false;
}
}
}
return ret;
}
void dance(int x)
{
if(x+ex()>=ans)return ;//A*優化
if(p[0].r==0)//得出答案
{
ans=x;
return ;
}
int first=0,mi=999999999;
for(int i=p[0].r;i;i=p[i].r)
{
if(size[p[i].lie]<mi)mi=size[p[i].lie],first=i;
}
if(mi==0)return ;
for(int i=p[first].u;i!=first;i=p[i].u)
{
del(i);//注意,不是del(first);
for(int j=p[i].l;j!=i;j=p[j].l)del(j);//不是p[j].lie
dance(x+1);
for(int j=p[i].r;j!=i;j=p[j].r)back(j);//回溯
back(i);//回溯
}
}
}dlx;
struct pointss
{
double x,y;
}lei[100],city[100];
inline double dis(pointss x,pointss y){return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));}//計算距離
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n,m,k;scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)scanf("%lf%lf",&city[i].x,&city[i].y);
for(int i=1;i<=m;i++)scanf("%lf%lf",&lei[i].x,&lei[i].y);//把他當成小數,方便以後計算dis
ll l=0,r=1e11,mid,anss;//將double轉成long long
while(l<=r)
{
mid=(l+r)/2;
double WXP=mid/100000000.0;//計算出半徑
dlx.clear(n);
for(int i=1;i<=m;i++)
{
a[0]=0;
for(int j=1;j<=n;j++)
{
if(dis(city[j],lei[i])<=WXP)a[++a[0]]=j;//建圖。
}
dlx.add(i);
}
ans=999999999;dlx.dance(0);
if(ans<=k)anss=mid,r=mid-1;
else l=mid+1;
}
printf("%.6lf\n",anss/100000000.0);//輸出
}
return 0;
}
數獨才講到
題目:
9*9數獨
約束條件:
- 一個格子填一個數字。
- 一行每個數字填一個。
- 一列每個數字填一個。
- 一宮每個數字填一個。
把約束條件變成列:
先把第一個約束條件寫出來,用81列來代表,在i行j列填代表覆蓋\((i-1)*9+j\)列
第二個約束條件,也用81列來代表,在第i行填k代表覆蓋\(81+(i-1)*9+k\)列
第三個約束條件,也用81列來代表,在第j列填k代表覆蓋\(162+(j-1)*9+k\)列
第四個約束條件,我們用一個公式計算出每個數字在第幾宮:\(((i-1)/3)*3+(j-1)/3+1\),而在第\(((i-1)/3)*3+(j-1)/3+1\)宮填k代表覆蓋\(243+(((i-1)/3)*3+(j-1)/3)*9+k\)列。
然後把第i行第j列填k代表行,處理出他能覆蓋那些列,建圖,然後跑一遍,OK
但是我們還可以優化,由於數獨中某些格子被填過,所以我們可以把這些格子所覆蓋的區域先刪掉,就可以達到優化時間的效果!
順便提一下,這裡面的行就可以起到帶有附加權值的效果。
至於程式碼。。。醜
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
int a[1000];
struct point
{
int x,y,c;
inline void zh(int tt){x=(tt-1)/81+1;y=((tt-1)%81)/9+1;c=((tt-1)%9)+1;}//將tt值轉回來
}ans[1000];
struct node
{
int l,r,u,d,lie,hang;
};
node p[210000];int len;
int size[1000],last[1000],map[11][11];
bool bol[1000];
inline void make(int l,int r,int u,int d,int lie,int hang){p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].lie=lie;p[len].hang=hang;}
inline void sxdel(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}
inline void nsxdel(int x){p[p[x].u].d=p[p[x].d].u=x;}
inline void zydel(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}
inline void nzydel(int x){p[p[x].l].r=p[p[x].r].l=x;}
inline void clear(int x)
{
len=0;p[0].l=p[0].r=p[0].u=p[0].d=0;
for(int i=1;i<=x;i++)
{
len++;make(i-1,p[i-1].r,i,i,i,0);
nzydel(i);size[i]=0;last[i]=i;
}
}
inline void add(int row)
{
if(!a[0])return ;
len++;make(len,len,last[a[1]],p[last[a[1]]].d,a[1],row);
nsxdel(len);size[a[1]]++;last[a[1]]=len;
for(int i=2;i<=a[0];i++)
{
len++;make(len-1,p[len-1].r,last[a[i]],p[last[a[i]]].d,a[i],row);
nsxdel(len);nzydel(len);size[a[i]]++;last[a[i]]=len;
}
}
inline void del(int x)
{
zydel(x);
for(int i=p[x].u;i!=x;i=p[i].u)
{
for(int j=p[i].r;j!=i;j=p[j].r)
{
sxdel(j),size[p[j].lie]--;
}
}
}
inline void back(int x)
{
nzydel(x);
for(int i=p[x].d;i!=x;i=p[i].d)
{
for(int j=p[i].l;j!=i;j=p[j].l)nsxdel(j),size[p[j].lie]++;
}
}
inline bool dance(int x)
{
//打了無數遍的程式碼
if(!p[0].r)
{
for(int i=1;i<=x;i++)
{
map[ans[i].x][ans[i].y]=ans[i].c;
}
for(int i=1;i<=9;i++)
{
for(int j=1;j<=8;j++)printf("%d ",map[i][j]);
printf("%d\n",map[i][9]);
}
return true;
}
int first=0,mi=999999999;
for(int i=p[0].r;i;i=p[i].r)
{
if(size[p[i].lie]<mi)mi=size[p[i].lie],first=p[i].lie;
}
if(mi==0)return false;
del(first);
for(int i=p[first].u;i!=first;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].lie);
ans[x+1].zh(p[i].hang);
if(dance(x+1))return true;
for(int j=p[i].r;j!=i;j=p[j].r)back(p[j].lie);
}
back(first);
return false;
}
int main()
{
memset(bol,false,sizeof(bol));//清0
clear(4*9*9);
for(int i=1;i<=9;i++)
{
for(int j=1;j<=9;j++)
{
scanf("%d",&map[i][j]);
if(map[i][j])
{
bol[(i-1)*9+j]=true;
// dlx.del((i-1)*9+j);
bol[81+(i-1)*9+map[i][j]]=true;
// dlx.del(81+(i-1)*9+dlx.map[i][j]);
bol[162+(j-1)*9+map[i][j]]=true;
// dlx.del(162+(j-1)*9+dlx.map[i][j]);
bol[243+(((i-1)/3)*3+(j-1)/3)*9+map[i][j]]=true;
// dlx.del(243+(((i-1)/3)*3+(j-1)/3)*9+dlx.map[i][j]);
}
else
{
for(int k=1;k<=9;k++)//列舉k
{
if(!bol[81+(i-1)*9+k] && !bol[162+(j-1)*9+k] && !bol[243+(((i-1)/3)*3+(j-1)/3)*9+k])
{
a[0]=0;
a[++a[0]]=(i-1)*9+j;
a[++a[0]]=81+(i-1)*9+k;
a[++a[0]]=162+(j-1)*9+k;
a[++a[0]]=243+(((i-1)/3)*3+(j-1)/3)*9+k;
add((i-1)*81+(j-1)*9+k);
}
}
}
}
}
for(int i=1;i<=324;i++)//這個要打在外面,因為如果在for迴圈裡面刪除的話,會因為last並沒有更新而導致在add裡面把刪除的節點重新還原
{
if(bol[i])del(i);
}
dance(0);
return 0;
}
浪費了我大好青春
練手題
八皇后:
傳送門
提示:通過想約束條件,構建矩陣,不過要想清楚,斜線的情況
不懂看程式碼:
//與數獨不同的地方標出來了
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
int a[1000],jie,anslen,now[50],n;
struct jians
{
int a[20];
}anss[100000];
inline bool cmp(jians x,jians y)
{
for(int i=1;i<=n;i++)
{
if(x.a[i]!=y.a[i])return x.a[i]<y.a[i];
}
return true;
}
struct node
{
int l,r,u,d,lie,hang;
};
struct DLX
{
node p[1000];int len;
int size[1000],last[1000];
inline void make(int l,int r,int u,int d,int lie,int hang){p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].lie=lie;p[len].hang=hang;}
inline void sxdel(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}
inline void nsxdel(int x){p[p[x].u].d=p[p[x].d].u=x;}
inline void zydel(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}
inline void nzydel(int x){p[p[x].l].r=p[p[x].r].l=x;}
inline void clear(int x)
{
len=0;p[0].l=p[0].r=p[0].u=p[0].r=0;size[0]=999999999;
for(int i=1;i<=x;i++)
{
len++;make(i-1,p[i-1].r,i,i,i,0);
nzydel(len);size[i]=0;last[i]=i;
}
}
inline void add(int row)
{
if(!a[0])return ;
len++;make(len,len,last[a[1]],p[last[a[1]]].d,a[1],row);
nsxdel(len);size[a[1]]++;last[a[1]]=len;
for(int i=2;i<=a[0];i++)
{
len++;make(len-1,p[len-1].r,last[a[i]],p[last[a[i]]].d,a[i],row);
nsxdel(len);nzydel(len);size[a[i]]++;last[a[i]]=len;
}
}
inline void del(int x)
{
zydel(x);
for(int i=p[x].u;i!=x;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)sxdel(j),size[p[j].lie]--;
}
}
inline void back(int x)
{
nzydel(x);
for(int i=p[x].u;i!=x;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)nsxdel(j),size[p[j].lie]++;
}
}
inline void dance(int x)
{
if(p[0].r>jie || !p[0].r)//這裡不一樣
{
anslen++;
for(int i=1;i<=n;i++)anss[anslen].a[i]=now[i];
return ;//記錄答案
}
int mi=0;
for(int i=p[0].r;i<=jie;i=p[i].r)//稍稍有不同
{
if(size[p[i].lie]<size[p[mi].lie])mi=i;
}
del(mi);
for(int i=p[mi].u;i!=mi;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].lie);
now[(p[i].hang-1)/n+1]=(p[i].hang-1)%n+1 ;
dance(x+1);
for(int j=p[i].r;j!=i;j=p[j].r)back(p[j].lie);
}
back(mi);
}
}dlx;
inline int mymin(int x,int y){return x<y?x:y;}
int main()
{
scanf("%d",&n);
dlx.clear(6*n-2);jie=2*n;//由於斜線有2n-1個,所以只需滿足橫線與豎線的要求
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
a[0]=0;
a[++a[0]]=i;
a[++a[0]]=n+j;
a[++a[0]]=(2*n+(j-i)+n);
a[++a[0]]=(4*n-1+(i+j)-1);
dlx.add((i-1)*n+j);//建圖不同
}
}
dlx.dance(0);
sort(anss+1,anss+anslen+1,cmp);//排序
int edd=mymin(3,anslen);
for(int i=1;i<=edd;i++)
{
for(int j=1;j<n;j++)printf("%d ",anss[i].a[j]);
printf("%d\n",anss[i].a[n]);
}
printf("%d\n",anslen);
return 0;
}
//右斜:j-i+n
//左斜:i+j-1
靶型數獨:
傳送門
DLX輕鬆AC:
#include<cstdio>
#include<cstring>
using namespace std;
int a[1000],ans=-1,now;
int p[11][11]=
{
{0,0,0,0,0,0,0,0,0,0,0},
{0,6,6,6,6,6,6,6,6,6,0},
{0,6,7,7,7,7,7,7,7,6,0},
{0,6,7,8,8,8,8,8,7,6,0},
{0,6,7,8,9,9,9,8,7,6,0},
{0,6,7,8,9,10,9,8,7,6,0},
{0,6,7,8,9,9,9,8,7,6,0},
{0,6,7,8,8,8,8,8,7,6,0},
{0,6,7,7,7,7,7,7,7,6,0},
{0,6,6,6,6,6,6,6,6,6,0}
};
struct node
{
int l,r,u,d,lie,hang,c;
};
struct DLX
{
node p[210000];int len;
int size[1000],last[1000];
bool bol[1000];
void make(int l,int r,int u,int d,int lie,int hang,int c){p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].lie=lie;p[len].hang=hang;p[len].c=c;}
void sxdel(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}
void nsxdel(int x){p[p[x].u].d=p[p[x].d].u=x;}
void zydel(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}
void nzydel(int x){p[p[x].r].l=p[p[x].l].r=x;}
void clear(int x)
{
len=0;p[0].l=p[0].r=p[0].u=p[0].d=0;size[0]=999999999;
for(int i=1;i<=x;i++)
{
len++;make(i-1,p[i-1].r,i,i,i,0,0);
nzydel(len);size[i]=0;last[i]=i;
}
}
void add(int row,int dis)
{
if(!a[0])return ;
len++;make(len,len,last[a[1]],p[last[a[1]]].d,a[1],row,dis);
nsxdel(len);size[a[1]]++;last[a[1]]=len;
for(int i=2;i<=a[0];i++)
{
len++;make(len-1,p[len-1].r,last[a[i]],p[last[a[i]]].d,a[i],row,dis);
nsxdel(len);nzydel(len);size[a[i]]++;last[a[i]]=len;
}
}
void del(int x)
{
zydel(x);
for(int i=p[x].u;i!=x;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)sxdel(j),size[p[j].lie]--;
}
}
void back(int x)
{
nzydel(x);
for(int i=p[x].u;i!=x;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)nsxdel(j),size[p[j].lie]++;
}
}
int ex(int x){return 4050-x;}//A*剪枝,大概就是說假設每個格子的分數都是10,總分數是多少
void dance(int x)
{
if(now+ex(now)<=ans)return ;//A*
if(!p[0].r)
{
if(now>ans)ans=now;//記錄答案
return ;
}
int mi=0;
for(int i=p[0].l;i;i=p[i].l)
{
if(size[p[i].lie]<size[p[mi].lie])mi=i;
}
if(!size[p[mi].lie])return ;
del(mi);
for(int i=p[mi].u;i!=mi;i=p[i].u)
{
for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].lie);
now+=p[i].c;
dance(x+1);
now-=p[i].c;
for(int j=p[i].r;j!=i;j=p[j].r)back(p[j].lie);
}
back(mi);
}
}dlx;
int main()
{
memset(dlx.bol,false,sizeof(dlx.bol));
dlx.clear(324);
for(int i=1;i<=9;i++)
{
for(int j=1;j<=9;j++)
{
int x;scanf("%d",&x);
if(x!=0)
{
dlx.bol[(i-1)*9+j]=true;
dlx.bol[81+(i-1)*9+x]=true;
dlx.bol[162+(j-1)*9+x]=true;
dlx.bol[243+(((i-1)/3)*3+(j-1)/3)*9+x]=true;
now+=x*p[i][j];
}
else
{
for(int k=9;k>=1;k--)
{
if(!dlx.bol[81+(i-1)*9+k] && !dlx.bol[162+(j-1)*9+k] && !dlx.bol[243+(((i-1)/3)*3+(j-1)/3)*9+k])
{
a[0]=0;
a[++a[0]]=(i-1)*9+j;
a[++a[0]]=81+(i-1)*9+k;
a[++a[0]]=162+(j-1)*9+k;
a[++a[0]]=243+(((i-1)/3)*3+(j-1)/3)*9+k;
dlx.add((i-1)*81+(j-1)*9+k,k*p[i][j]);//建圖
}
}
}
}
}
for(int i=1;i<=324;i++)//數獨的剪枝
{
if(dlx.bol[i])dlx.del(i);
}
dlx.dance(0);
printf("%d\n",ans);
return 0;
}
完結撒花