DLX算法


理解DLX算法之前首先了解精確覆蓋問題和重復覆蓋問題

精確覆蓋問題

何為精確覆蓋問題

  在一個全集X中若干子集的集合為S,精確覆蓋(Exactcover)是指,S的子集S*,滿足X中的每一個元素在S*中恰好出現一次。

定義 

  滿足以下條件的集合為一個精確覆蓋: 
    S*中任意兩個集合沒有交集,即X中的元素在S*中出現最多一次 
    S*中集合的全集為X,即X中的元素在S*中出現最少一次 
    合二為一,即X中的元素在S*中出現恰好一次。
  舉例
  令={N,O,E,P}是集合X={1,2,3,4}的一個子集,並滿足: 
    N={} 
    O={1,3} 
    E={2,4} 
    P={2,3}. 
    其中一個子集{O,E}是X的一個精確覆蓋,因為O={1,3}而E={2,4}的並集恰好是X={1,2,3,4}。同理,{N,O,E}也是X.的一個精確覆蓋。空集並不影響結論。

精確覆蓋問題的表示方式

  一般的,我們用一個集合s包含s中的元素的單向關系表示精確覆蓋問題。常用的有以下兩種方法:

  • 矩陣表示法

  包含關系可以用一個關系矩陣表示。.矩陣每行表示S的一個子集,每列表示X中的一個元素。矩陣行列交點元素為1表示對應的元素在對應的集合中,不在則為0。 

  通過這種矩陣表示法,求一個精確覆蓋轉化為求矩陣的若干個行的集合,使每列有且僅有一個1。同時,該問題也是精確覆蓋的典型例題之一。

  下表為其中一個例子:

S*={B,D,F}便是一個精確覆蓋。

  • 圖論表示法

  可將精確覆蓋問題轉化為一個二分圖,左側為集合,右側為元素,左側集合若與右側元素有包含關系則連邊,通過將左側節點與其所有邊保留與否求解一個右側的每一個節點恰好有一條邊的匹配。

重復覆蓋問題

  即選取一個01矩陣中的幾行,使這幾行組成的新矩陣的每一列至少有一個1,也就是說每一列上可以有多個1。 該問題在精確覆蓋問題上減少了一個約束條件。

Dancing Links X 算法

歷史

  算法大師Donald E.Knuth(《計算機程序設計藝術》的作者)提出了DLX(Dancing Links X)算法。實際上,他把上面求解的過程稱為X算法,而他提出的舞蹈鏈(Dancing Links)實際上並不是一種算法,而是一種數據結構。一種非常巧妙的數據結構,他的數據結構在緩存和回溯的過程中效率驚人,不需要額外的空間,以及近乎線性的時間。而在整個求解過程中,指針在數據之間跳躍着,就像精巧設計的舞蹈一樣,故Donald E.Knuth把它稱為Dancing Links(中文譯名舞蹈鏈)。

算法思想

  

Dancing Links的核心是基於雙向鏈的方便操作(移除、恢復加入)

我們用例子來說明

假設雙向鏈的三個連續的元素,A1、A2、A3,每個元素有兩個分量Left和Right,分別指向左邊和右邊的元素。由定義可知

A1.Right=A2,A2.Right=A3

A2.Left=A1,A3.Left=A2

在這個雙向鏈中,可以由任一個元素得到其他兩個元素,A1.Right.Right=A3,A3.Left.Left=A1等等

 

現在把A2這個元素從雙向鏈中移除(不是刪除)出去,那么執行下面的操作就可以了

A1.Right=A3,A3.Left=A1

那么就直接連接起A1和A3。A2從雙向鏈中移除出去了。但僅僅是從雙向鏈中移除了,A2這個實體還在,並沒有刪除。只是在雙向鏈中遍歷的話,遍歷不到A2了。

那么A2這個實體中的兩個分量Left和Right指向誰?由於實體還在,而且沒有修改A2分量的操作,那么A2的兩個分量指向沒有發生變化,也就是在移除前的指向。即A2.Left=A1和A2.Right=A3

 

如果此時發現,需要把A2這個元素重新加入到雙向鏈中的原來的位置,也就是A1和A3的中間。由於A2的兩個分量沒有發生變化,仍然指向A1和A3。那么只要修改A1的Right分量和A3的Left就行了。也就是下面的操作

A1.Right=A2,A3.Left=A2

 

仔細想想,上面兩個操作(移除和恢復加入)對應了什么?是不是對應了之前的算法過程中的關鍵的兩步?

移除操作對應着緩存數據、恢復加入操作對應着回溯數據。而美妙的是,這兩個操作不再占用新的空間,時間上也是極快速的

 

在很多實際運用中,把雙向鏈的首尾相連,構成循環雙向鏈

 

Dancing Links用的數據結構是交叉十字循環雙向鏈

而Dancing Links中的每個元素不僅是橫向循環雙向鏈中的一份子,又是縱向循環雙向鏈的一份子。

因為精確覆蓋問題的矩陣往往是稀疏矩陣(矩陣中,0的個數多於1),Dancing Links僅僅記錄矩陣中值是1的元素。

 

Dancing Links中的每個元素有6個分量

分別:Left指向左邊的元素、Right指向右邊的元素、Up指向上邊的元素、Down指向下邊的元素、Col指向列標元素、Row指示當前元素所在的行

 

Dancing Links還要准備一些輔助元素(為什么需要這些輔助元素?沒有太多的道理,大師認為這能解決問題,實際上是解決了問題)

Ans():Ans數組,在求解的過程中保留當前的答案,以供最后輸出答案用。

Head元素:求解的輔助元素,在求解的過程中,當判斷出Head.Right=Head(也可以是Head.Left=Head)時,求解結束,輸出答案。Head元素只有兩個分量有用。其余的分量對求解沒啥用

C元素:輔助元素,稱列標元素,每列有一個列標元素。本文開始的題目的列標元素分別是C1、C2、C3、C4、C5、C6、C7。每一列的元素的Col分量都指向所在列的列標元素。列標元素的Col分量指向自己(也可以是沒有)。在初始化的狀態下,Head.Right=C1、C1.Right=C2、……、C7.Right=Head、Head.Left=C7等等。列標元素的分量Row=0,表示是處在第0行。

 

下圖就是根據題目構建好的交叉十字循環雙向鏈(構建的過程后面的詳述)

 

就上圖解釋一下

每個綠色方塊是一個元素,其中Head和C1、C2、……、C7是輔助元素。橙色框中的元素是原矩陣中1的元素,給他們標上號(從1到16)

左側的紅色,標示的是行號,輔助元素所在的行是0行,其余元素所在的行從1到6

每兩個元素之間有一個雙向箭頭連線,表示雙向鏈中相鄰兩個元素的關系(水平的是左右關系、垂直的是上下關系)

單向的箭頭並不是表示單向關系,而因為是循環雙向鏈,左側的單向箭頭和右側的單向箭頭(上邊的和下邊的)組成了一個雙向箭頭,例如元素14左側的單向箭頭和元素16右側的單項箭頭組成一個雙向箭頭,表示14.Left=16、16.Right=14;同理,元素14下邊的單項箭頭和元素C4上邊的單向箭頭組成一個雙向箭頭,表示14.Down=C4、C4.Up=14

 

接下來,利用圖來解釋Dancing Links是如何求解精確覆蓋問題

1、首先判斷Head.Right=Head?若是,求解結束,輸出解;若不是,求解還沒結束,到步驟2(也可以判斷Head.Left=Head?)

2、獲取Head.Right元素,即元素C1,並標示元素C1標示元素C1,指的是標示C1、和C1所在列的所有元素、以及該元素所在行的元素,並從雙向鏈中移除這些元素)。如下圖中的紫色部分。

image

如上圖可知,行2和行4中的一個必是答案的一部分(其他行中沒有元素能覆蓋列C1),先假設選擇的是行2

 

3、選擇行2(在答案棧中壓入2),標示該行中的其他元素(元素5和元素6)所在的列首元素,即標示元素C4標示元素C7,下圖中的橙色部分。

注意的是,即使元素5在步驟2中就從雙向鏈中移除,但是元素5的Col分量還是指向元素C4的,這里體現了雙向鏈的強大作用。

image

 

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

image

一下子空了好多,是不是轉換為一個少了很多元素的精確覆蓋問題?,利用遞歸的思想,很快就能寫出求解的過程來。我們繼續完成求解過程

 

4、獲取Head.Right元素,即元素C2,並標示元素C2。如下圖中的紫色部分。

image

如圖,列C2只有元素7覆蓋,故答案只能選擇行3

 

5、選擇行3(在答案棧中壓入3),標示該行中的其他元素(元素8和元素9)所在的列首元素,即標示元素C3標示元素C6,下圖中的橙色部分。

image

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

image

 

6、獲取Head.Right元素,即元素C5,元素C5中的垂直雙向鏈中沒有其他元素,也就是沒有元素覆蓋列C5。說明當前求解失敗。要回溯到之前的分叉選擇步驟(步驟2)。那要回標列首元素(把列首元素、所在列的元素,以及對應行其余的元素。並恢復這些元素到雙向鏈中),回標列首元素的順序是標示元素的順序的反過來。從前文可知,順序是回標列首C6回標列首C3回標列首C2回標列首C7回標列首C4。表面上看起來比較復雜,實際上利用遞歸,是一件很簡單的事。並把答案棧恢復到步驟2(清空的狀態)的時候。又回到下圖所示

image

 

7、由於之前選擇行2導致無解,因此這次選擇行4(再無解就整個問題就無解了)。選擇行4(在答案棧中壓入4),標示該行中的其他元素(元素11)所在的列首元素,即標示元素C4,下圖中的橙色部分。

image

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

image

 

8、獲取Head.Right元素,即元素C2,並標示元素C2。如下圖中的紫色部分。

image

如圖,行3和行5都可以選擇

 

9、選擇行3(在答案棧中壓入3),標示該行中的其他元素(元素8和元素9)所在的列首元素,即標示元素C3標示元素C6,下圖中的橙色部分。

image

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

image

 

10、獲取Head.Right元素,即元素C5,元素C5中的垂直雙向鏈中沒有其他元素,也就是沒有元素覆蓋列C5。說明當前求解失敗。要回溯到之前的分叉選擇步驟(步驟8)。從前文可知,回標列首C6回標列首C3。並把答案棧恢復到步驟8(答案棧中只有4)的時候。又回到下圖所示

image

 

11、由於之前選擇行3導致無解,因此這次選擇行5(在答案棧中壓入5),標示該行中的其他元素(元素13)所在的列首元素,即標示元素C7,下圖中的橙色部分。

image

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

image

 

12、獲取Head.Right元素,即元素C3,並標示元素C3。如下圖中的紫色部分。

image

 

13、如上圖,列C3只有元素1覆蓋,故答案只能選擇行3(在答案棧壓入1)。標示該行中的其他元素(元素2和元素3)所在的列首元素,即標示元素C5標示元素C6,下圖中的橙色部分。

image

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

image

 

14、因為Head.Right=Head。故,整個過程求解結束。輸出答案,答案棧中的答案分別是4、5、1。表示該問題的解是第4、5、1行覆蓋所有的列。如下圖所示(藍色的部分)

image

 

從以上的14步來看,可以把Dancing Links的求解過程表述如下

 

1、Dancing函數的入口

2、判斷Head.Right=Head?,若是,輸出答案,返回True,退出函數。

3、獲得Head.Right的元素C

4、標示元素C

5、獲得元素C所在列的一個元素

6、標示該元素同行的其余元素所在的列首元素

7、獲得一個簡化的問題,遞歸調用Daning函數,若返回的True,則返回True,退出函數。

8、若返回的是False,則回標該元素同行的其余元素所在的列首元素,回標的順序和之前標示的順序相反

9、獲得元素C所在列的下一個元素,若有,跳轉到步驟6

10、若沒有,回標元素C,返回False,退出函數。

 

之前的文章的表述,為了表述簡單,采用面向對象的思路,說每個元素有6個分量,分別是Left、Right、Up、Down、Col、Row分量。

但在實際的編碼中,用數組也能實現相同的作用。例如:用Left()表示所有元素的Left分量,Left(1)表示元素1的Left分量

在前文中,元素分為Head元素、列首元素(C1、C2等)、普通元素。在編碼中,三種元素統一成一種元素。如上題,0表示Head元素,1表示元素C1、2表示元素C2、……、7表示元素C7,從8開始表示普通元素。這是統一后,編碼的簡便性。利用數組的下標來表示元素,宛若指針一般。

 

精確覆蓋例題:Sudoku ZOJ - 3122   鏈接:https://zoj.pintia.cn/problem-sets/91827364500/problems/91827367537

代碼:

  1 #include <cstdio>
  2 #include <fstream>
  3 #include <algorithm>
  4 #include <cmath>
  5 #include <deque>
  6 #include <vector>
  7 #include <queue>
  8 #include <string>
  9 #include <cstring>
 10 #include <map>
 11 #include <stack>
 12 #include <set>
 13 #include <sstream>
 14 #include <iostream>
 15 #define mod 1000000007
 16 #define eps 1e-6
 17 #define ll long long
 18 #define INF 0x3f3f3f3f
 19 using namespace std;
 20 
 21 const int maxn=18000;
 22 //ans用來記錄答案的編號
 23 int ans[maxn];
 24 struct DLX
 25 {
 26 
 27     //左右上下,四個數組
 28     int left[maxn],right[maxn],up[maxn],down[maxn];
 29     //列數組,頭數組,loc代表這個數在數獨中的位置和數值
 30     int col[maxn],head[maxn],loc[maxn][3];
 31     //num數組保存每列有幾個數,id為編號
 32     int num[1030],id;
 33     //創建有m列的矩陣
 34     void init(int n)
 35     {
 36         for(int i=0;i<=n;i++)
 37         {
 38             up[i]=down[i]=i;
 39             left[i]=i-1;
 40             right[i]=i+1;
 41         }
 42         left[0]=n; right[n]=0;
 43         id=n;
 44         memset(num,0,sizeof(num));
 45         memset(head,-1,sizeof(head));
 46     }
 47     //插入位於x,y的數,並對其上下左右,列和編號初始化,對px,py,pz存入loc數組
 48     void Link(int x,int y,int px,int py,int k)
 49     {
 50         ++id;
 51         down[id]=y; 
 52         up[id]=up[y];
 53         down[up[y]]=id;
 54         up[y]=id;
 55         loc[id][0]=px,loc[id][1]=py,loc[id][2]=k;//存放數的位置和數
 56         col[id]=y;
 57         num[y]++;//此列1的數量加一
 58         if(head[x]==-1)
 59         {
 60                 head[x]=left[id]=right[id]=id;
 61         }
 62         else
 63         {
 64             int a=head[x];
 65             int b=right[a];
 66             left[id]=a; right[a]=id;
 67             right[id]=b; left[b]=id;
 68             head[x]=id;
 69         }
 70     }
 71     //移除c列和c列上數所在的每一行,
 72     void Remove(int c)
 73     {
 74         left[right[c]]=left[c];
 75         right[left[c]]=right[c];
 76         for(int i=down[c];i!=c;i=down[i])
 77             for(int j=right[i];j!=i;j=right[j])
 78         {
 79             up[down[j]]=up[j];
 80             down[up[j]]=down[j];
 81             num[col[j]]--;
 82         }
 83     }
 84     //恢復c列和c列上數所在的每一行,
 85     void Resume(int c)
 86     {
 87         for(int i=up[c];i!=c;i=up[i])
 88             for(int j=right[i];j!=i;j=right[j])
 89         {
 90             num[col[j]]++;
 91             up[down[j]]=j;
 92             down[up[j]]=j;
 93         }
 94         left[right[c]]=c;
 95         right[left[c]]=c;
 96     }
 97     bool dfs(int step)
 98     {
 99         //如果走到第256步時已走完所有的數獨中的數,所以退出
100         if(step==256) return true;
101         //如果頭指向第0列,說明所有列已刪除
102         if(right[0]==0) return false;
103         int c=right[0];
104         //用循環是c優先指向列中數少的列
105         for(int i=right[0];i;i=right[i])
106         {
107              if(num[i]<num[c])
108              { 
109                  c=i;
110              }
111         }
112         //刪除第c列
113         Remove(c);
114         for(int i=down[c];i!=c;i=down[i])
115         {
116             //記錄每此循環選的編號
117             ans[step]=i;
118             //遍歷i所在的行,並刪除j所在的列
119             for(int j=right[i];j!=i;j=right[j]) Remove(col[j]);
120             //如果循環下去有解,則返回true
121             if(dfs(step+1)) return true;
122             //遍歷i所在的行,並恢復j所在的列
123             for(int j=left[i];j!=i;j=left[j]) Resume(col[j]);
124         }
125         //恢復第c列
126         Resume(c);
127         //所有操作完成后仍無解,則返回false
128         return false;
129     }
130 }dlx;
131 int main()
132 {
133     //str數組存放輸入的數據
134     char str[260];
135     int kase=0;
136     while(cin>>str)
137     {
138         //換行
139         if(kase)
140         {
141             cout<<endl;
142         }
143         kase++;
144         for(int i=1;i<=15;i++)
145         {
146             cin>>str+i*16;
147         }
148         dlx.init(256*4);
149         int r=0,js=0;//r代表行
150         for(int x=0;x<16;x++)
151         {    for(int y=0;y<16;y++)
152             {
153                 char ch=str[js];
154                 js++;
155                 int s=(x/4)*4+y/4;//
156                 int a,b,c,d;//a表示約束一,b表示約束二,c表示約束三,d表示約束四
157                 if(ch=='-')
158                 {
159                     //此位置上可能是1到16,
160                     for(int i=1;i<=16;i++)
161                     {
162                         a=x*16+y+1;
163                         b=x*16+i+256;
164                         c=y*16+i+256+256;
165                         d=s*16+i+256+256+256;
166                         ++r;
167                         dlx.Link(r,a,x,y,i);
168                         dlx.Link(r,b,x,y,i);
169                         dlx.Link(r,c,x,y,i);
170                         dlx.Link(r,d,x,y,i);
171                     }
172                 }
173                 else
174                 {
175                     int i=ch-64;
176                     a=x*16+y+1;
177                     b=x*16+i+256;
178                     c=y*16+i+256+256;
179                     d=s*16+i+256+256+256;
180                     ++r;
181                     dlx.Link(r,a,x,y,i);
182                     dlx.Link(r,b,x,y,i);
183                     dlx.Link(r,c,x,y,i);
184                     dlx.Link(r,d,x,y,i);
185                 }
186             }
187         }
188         dlx.dfs(0);
189         char res[16][16];
190         for(int i=0;i<256;i++)//將答案存放到一個數獨數組中
191         {
192             int a=ans[i];
193             int x=dlx.loc[a][0],y=dlx.loc[a][1],k=dlx.loc[a][2]-1;
194             res[x][y]=k+'A';
195         }
196         for(int i=0;i<16;i++)
197         {
198             for(int j=0;j<16;j++)
199             {
200                 printf("%c",res[i][j]);
201             }
202             printf("\n");
203         }
204     }
205    
206 }

 

重復覆蓋例題:Airport HDU - 5046  鏈接:https://vjudge.net/problem/HDU-5046

代碼:

  1 #include <cstdio>
  2 #include <fstream>
  3 #include <algorithm>
  4 #include <cmath>
  5 #include <deque>
  6 #include <vector>
  7 #include <queue>
  8 #include <string>
  9 #include <cstring>
 10 #include <map>
 11 #include <stack>
 12 #include <set>
 13 #include <sstream>
 14 #include <iostream>
 15 #define mod 998244353
 16 #define eps 1e-6
 17 #define ll long long
 18 #define INF 0x3f3f3f3f
 19 using namespace std;
 20 
 21 const int maxn=4010;
 22 int k;
 23 struct 
 24 {
 25     //左右上下,四個數組
 26     int left[maxn],right[maxn],up[maxn],down[maxn];
 27     //頭數組,列數組
 28     int head[65],col[maxn];
 29     //num數組保存每列有幾個數,id為編號
 30     int num[65],id;
 31     //創建有m列的矩陣
 32     void init(int m)
 33     {
 34         for(int i=0;i<=m;i++)
 35         {
 36             left[i]=i-1;
 37             right[i]=i+1;
 38             up[i]=down[i]=i;
 39             col[i]=i;
 40         }
 41         id=m;
 42         left[0]=m;
 43         right[m]=0;
 44         memset(head,-1,sizeof(head));
 45         memset(num,0,sizeof(num));
 46     }
 47     //插入位於x,y的數,並對其上下左右,列和編號初始化
 48     void link(int x,int y)
 49     {
 50         id++;
 51         down[id]=down[y];
 52         up[down[y]]=id;
 53         up[id]=y;
 54         down[y]=id;
 55         num[y]++;
 56         col[id]=y;
 57         if(head[x]==-1)
 58         {
 59             head[x]=left[id]=right[id]=id;
 60         }
 61         else
 62         {
 63             right[id]=right[head[x]];
 64             left[right[head[x]]]=id;
 65             left[id]=head[x];
 66             right[head[x]]=id;
 67         }
 68     }
 69     //只移除c所在的一列和c所在列上數所在的每一行
 70     void remove(int c)
 71     {
 72         for(int i=down[c];i!=c;i=down[i])
 73         {
 74             left[right[i]]=left[i];
 75             right[left[i]]=right[i];
 76         }
 77     }
 78     //恢復c所在的一列和c所在列上數所在的每一行
 79     void reback(int c)
 80     {
 81         for(int i=up[c];i!=c;i=up[i])
 82         {
 83             left[right[i]]=right[left[i]]=i;
 84         }
 85     }
 86     bool bj[maxn];
 87     //計算還需要多少步
 88     int A()
 89     {
 90         int ans=0;
 91         for(int c=right[0];c!=0;c=right[c])
 92         {
 93             bj[c]=true;
 94         }
 95         for(int c=right[0];c!=0;c=right[c])
 96         {
 97             if(bj[c])
 98             {
 99                 ans++;
100                 bj[c]=false;
101                 for(int i=down[c];i!=c;i=down[i])
102                 {
103                     for(int j=right[i];j!=i;j=right[j])
104                     {
105                         bj[col[j]]=false;
106                     }
107                 }
108             }
109         }
110         return ans;
111     }
112     //核心函數,step表示步數
113     bool danc(int step)
114     {
115         //如果還要走的步數加上已走的步數,比現有的答案多,則不需要走了,因此退出
116         if(A()+step>k)//剪枝
117         {
118             return false;
119         }
120         //如果頭指向第0列,說明所有列已刪除
121         if(right[0]==0)
122         {
123             return step<=k;
124         }
125         int c=right[0];
126         //用循環是c優先指向列中數少的列
127         for(int i=c;i!=0;i=right[i])
128         {
129             if(num[i]<num[c])
130             {
131                 c=i;
132             }
133         }
134         //遍歷c列中的數
135         for(int i=down[c];i!=c;i=down[i])
136         {
137             //刪除i列
138             remove(i);
139             //遍歷i所在的行,並刪除j所在的列
140             for(int j=right[i];j!=i;j=right[j])
141             {
142                 remove(j);
143             }
144             //如果循環下去有解,則返回true
145             if(danc(step+1))
146             {
147                 return true;
148             }
149             //遍歷i所在的行,並恢復j所在的列
150             for(int j=left[i];j!=i;j=left[j])
151             {
152                 reback(j);
153             }
154             //恢復i列
155             reback(i);
156         }
157         //所有操作完成后仍無解,則返回false
158         return false;
159     }
160 }dlx;
161 struct node//小島信息
162 {
163     ll x,y;
164 }no[65];
165 ll dis(node a,node b)//計算距離
166 {
167     ll dx=a.x-b.x;
168     if(dx<0)
169     {
170         dx=-dx;
171     }
172     ll dy=a.y-b.y;
173     if(dy<0)
174     {
175         dy=-dy;
176     }
177     return dx+dy;
178 }
179 int main()
180 {
181     int t,ans=0;;
182     scanf("%d",&t);
183     while(t--)
184     {
185         ans++;
186         int n;
187         ll x[65],y[65];
188         scanf("%d %d",&n,&k);
189         for(int i=1;i<=n;i++)
190         {
191             scanf("%lld %lld",&no[i].x,&no[i].y);
192         }
193         ll le=0,rig=100000000000LL;
194         //二分法一步一步縮小距離
195         while(rig-le>0)
196         {
197             dlx.init(n);
198             ll mid=(rig+le)/2;
199             for(int i=1;i<=n;i++)
200             {
201                 for(int j=1;j<=n;j++)
202                 {
203                     if(dis(no[i],no[j])<=mid)//島之間的距離比航距小時
204                     {
205                         dlx.link(i,j);
206                     }
207                 }
208             }
209             if(dlx.danc(0))//如果當前航距需要的最少飛機的數量比k小為true
210             {
211                 rig=mid;
212             }
213             else
214             {
215                 le=mid+1;
216             }
217         }
218         printf("Case #%d: %lld\n",ans,le);
219     }
220 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM