舞蹈鏈(DLX)學習筆記

簡介

用途:解決精確/重複覆蓋問題(某一列含有恰好1個1),全稱Dancing Links X,X是未知搜索算法的意思。

洛谷模板題

超詳細的算法圖解

實際上並不難,就是 暴搜 + 十字鏈表維護。
把所有的1拿出來建十字鏈表,枚舉每一列被哪一行覆蓋,然後去掉跟這一列以及這一行有關的1,遞歸求解。

每個點記錄6個值,l,r,u,d,col,rowl,r,u,d,col,row,分別表示左/右/上/下的點,以及它所在的行和列。循環鏈接。

額外需要一個頭 headhead,一排點表示列的代表點 cic_i,第 ii 列被覆蓋就把 cic_i 的兩邊接起來。如果 r[head]=0r[head]=0 則說明所有列都被覆蓋了。

d[ci]d[c_i]指向第 ii 列的第一個點,第 ii 列的最後一個點指向 cic_i

刪除和恢復順序相反。

枚舉當前列選哪一行時找點數最少的列,減少枚舉次數。額外記變量 sis_i 表示第 ii 列 1 的個數。
如果某一個 cic_i 還存在但是 si=0s_i=0,說明覆蓋不了了,return false\texttt{return~false}

精確覆蓋

P4929洛谷模板題
注意空間要開 1 的個數 + 列的長度
下面的代碼沒有寫link,如果寫的話因爲行的順序不重要(記錄了row),所以新加一個點可以接在列點的下方。

#include<bits/stdc++.h>
#define maxn 5505
using namespace std;
void read(int &a){
	char c;while(!isdigit(c=getchar()));
	for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
int n,m,cnt,l[maxn],r[maxn],u[maxn],d[maxn],s[maxn],col[maxn],row[maxn],ans[maxn];
void remove(int c){//delete 1 linked to the c column
	r[l[c]]=r[c],l[r[c]]=l[c];
	for(int i=d[c];i!=c;i=d[i])
		for(int j=r[i];j!=i;j=r[j])
			d[u[j]]=d[j],u[d[j]]=u[j],s[col[j]]--;
}
void recover(int c){
	for(int i=u[c];i!=c;i=u[i])
		for(int j=l[i];j!=i;j=l[j])
			d[u[j]]=u[d[j]]=j,s[col[j]]++;
	r[l[c]]=l[r[c]]=c;
}
bool dance(int dep){
	if(!r[0]){
		for(int i=1;i<dep;i++) printf("%d%c",ans[i],i==dep-1?10:32);
		return 1;
	}
	int c=r[0];
	for(int i=r[c];i!=0;i=r[i]) if(s[i]<s[c]) c=i;
	if(!s[c]) return 0;
	remove(c);
	for(int i=d[c];i!=c;i=d[i]){
		ans[dep]=row[i];
		for(int j=r[i];j!=i;j=r[j]) remove(col[j]);
		if(dance(dep+1)) return 1;
		for(int j=l[i];j!=i;j=l[j]) recover(col[j]);
	}
	recover(c);
	return 0;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<=m;i++) u[i]=d[i]=i,l[i]=i-1,r[i]=i+1;
	l[r[m]=0]=cnt=m;
	for(int i=1;i<=n;i++)
		for(int j=1,hd=0,x;j<=m;j++) if(read(x),x){
			s[j]++,cnt++,row[cnt]=i,col[cnt]=j;
			if(hd) l[r[cnt]=hd]=r[l[cnt]=cnt-1]=cnt;
			else l[cnt]=r[cnt]=hd=cnt;
			int pre=u[j];
			u[d[cnt]=j]=d[u[cnt]=pre]=cnt;
		}
	if(!dance(1)) puts("No Solution!");
}

重複覆蓋

重複覆蓋因爲一列可以有多個點,所以刪除一列時只需要刪去這一列的點,而不再刪列上點左右鏈上的點。而刪列的時候要改左右指針,所以刪除和恢復的函數和精確覆蓋有些不同,不能一開始將一列的左右全改了,而是從選的那一行中橫向找1然後刪除對應的列。

要刪只會完整地刪一列,ss 不會變,開頭沒有remove也不用判s[c]0s[c]\neq 0,一開始就會return。

一般求的是最小重複覆蓋,需要加上估價函數剪枝。

HDU2295 Radar 題解

Code:

#include<bits/stdc++.h>
#define maxn 3005
using namespace std;
int k,cnt,l[maxn],r[maxn],u[maxn],d[maxn],h[maxn],s[maxn],col[maxn];
void init(int n,int m){
	for(int i=0;i<=m;i++) s[i]=0,u[i]=d[i]=i,l[i]=i-1,r[i]=i+1;
	l[r[m]=0]=cnt=m;
	for(int i=1;i<=n;i++) h[i]=0;
}
void link(int i,int j){
	s[col[++cnt]=j]++;
	int nxt=d[j]; d[u[cnt]=j]=u[d[cnt]=nxt]=cnt;
	if(!h[i]) l[cnt]=r[cnt]=h[i]=cnt;
	else {int pre=l[h[i]]; l[r[cnt]=h[i]]=r[l[cnt]=pre]=cnt;}
}
void remove(int i){
	for(int j=d[i];j!=i;j=d[j]) l[r[j]]=l[j],r[l[j]]=r[j];
}
void recover(int i){
	for(int j=u[i];j!=i;j=u[j]) r[l[j]]=l[r[j]]=j;
}
int E(){
	static bool v[maxn]; int ret=0;
	for(int i=r[0];i;i=r[i]) v[i]=0;
	for(int c=r[0];c;c=r[c]) if(!v[c]){
		ret++,v[c]=1;
		for(int i=d[c];i!=c;i=d[i])
			for(int j=r[i];j!=i;j=r[j])
				v[col[j]]=1;
	}
	return ret;
}
bool dance(int dep){
	if(dep+E()>k) return 0;
	if(!r[0]) return 1;
	int c=r[0];
	for(int i=r[c];i!=0;i=r[i]) if(s[i]<s[c]) c=i;
	for(int i=d[c];i!=c;i=d[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;
}
const int N = 55;
struct node{int x,y;}a[N],b[N];
int n,m,dis[N*N],id[N*N];
bool cmp(int i,int j){return dis[i]<dis[j];}
int main()
{
	int T; scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
		for(int i=1;i<=m;i++) scanf("%d%d",&b[i].x,&b[i].y);
		for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) 
			dis[(i-1)*m+j]=(a[i].x-b[j].x)*(a[i].x-b[j].x)+(a[i].y-b[j].y)*(a[i].y-b[j].y);
		for(int i=1;i<=n*m;i++) id[i]=i; sort(id+1,id+1+n*m,cmp);
		int l=1,r=n*m,mid;
		while(l<r){
			mid=(l+r)>>1;
			init(m,n);
			for(int i=1;i<=mid;i++) link((id[i]-1)%m+1,(id[i]-1)/m+1);
			if(dance(0)) r=mid;
			else l=mid+1;
		}
		printf("%.6f\n",sqrt(dis[id[l]]));
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章