DLX解决3-SAT问题

1.关于DLX的重复覆盖:根据与精确覆盖概念的区别可知,只需改变remove()和resume()函数控制删除和恢复的过程即可实现,对于求解最少步数问题,可借助ida*中的h()函数优化。

2.重复覆盖+精确覆盖:某些元素可重复覆盖(目标),而某些元素只能精确覆盖(每类元素只能使用一次),这是要对前m列进行重复覆盖的删除回复操作,对后面的列进行精确覆盖的操作,也有一些特殊情况可直接使用重复覆盖代替。如hdu2828,每类元素只有两种,可通过visit数组在选中某个元素时删除同类中的另一个元素,实现精确覆盖)。

3.K-sat问题:

考虑CNF

Φ=C1∧C2∧...∧Ci∧...Cn


子句Ci具有如下形式

Xi1Xi2∨...∨Xij∨...∨Xili


Xij为命题变元集Xij为{x1,x2....xm}中的一个变元,其中Xij称为正文字,Xij称为负文字,子句Ci中文字的个数定义为子句的长度,用li表示。对于一个子句Ci,只要其中一个文字为真,则称该子句是可满足的。
一个判定形式的SAT问题是指:对于给定的CNF是否存在一组关于命题的变元的真值赋值使得为真。
特殊的,当每个子句的长度为K时,则成为K-SAT问题。

以3-SAT为例(xmu1101),有遗传算法和dlx两种做法,这里介绍后者。

问题:有n个元素,每个元素有选和不选两种状态,因此可以看做n*2中元素;m个约束,每个约束要求某3个元素中至少选一个。目标:能否从2*n个元素中选择一些元素使其满足所有约束。

建模:由于要是元素m种约束,因此把m种约束看做重复覆盖的列,把2*n中元素看做行,如果第i个元素在第j个约束中,则mat[i][j]=1,同时每个元素只能有一种状态,因此将n个元素作为精确覆盖的列mat[i][m+i]=mat[i+n][m+i]=1,求解是否存在满足条件的覆盖即可,输出方案也很简单。

import java.util.Arrays;
import java.util.Scanner;


public class SAT_3 {

	class DLX {
		int maxn = 1010, inf = 1 << 28;
		int L[] = new int[maxn], R[] = new int[maxn], D[] = new int[maxn],
				U[] = new int[maxn];
		int Row[] = new int[maxn], C[] = new int[maxn], S[] = new int[maxn];
		// 元素x所在行列 每列元素个数
		int m, id, rowid;
		void init(int m) {
			this.m = m;
			for (int i = 0; i <= m; i++) {
				D[i] = U[i] = i;
				S[i] = 0;
				L[i] = i - 1;
				R[i] = i + 1;
			}
			L[0] = m;
			R[m] = 0;
			id = m + 1;
			rowid = 1;
		}
		void insert(int arr[], int len) {
			for (int i = 0; i < len; i++, id++) {
				int x = arr[i];
				C[id] = x;
				Row[id] = rowid;
				S[x]++;
				D[id] = x;
				U[id] = U[x];
				D[U[x]] = id;
				U[x] = id;
				if (i == 0)
					L[id] = R[id] = id;
				else {
					L[id] = id - 1;
					R[id] = id - i;
					L[id - i] = id;
					R[id - 1] = id;
				}
			}
			rowid++;
		}
		void remove(int c) {
			for (int i = U[c]; i != c; i = U[i]) {
				L[R[i]] = L[i];
				R[L[i]] = R[i];
			}
		}
		void resume(int c) {
			for (int i = D[c]; i != c; i = D[i]) {
				L[R[i]] = i;
				R[L[i]] = i;
			}
		}
		void ExRemove(int c) {
			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]) {
					S[C[j]]--;
					U[D[j]] = U[j];
					D[U[j]] = D[j];
				}

		}
		void ExResume(int c) {
			for (int i = U[c]; i != c; i = U[i])
				for (int j = L[i]; j != i; j = L[j]) {
					S[C[j]]++;
					U[D[j]] = j;
					D[U[j]] = j;
				}
			L[R[c]] = c;
			R[L[c]] = c;
		}
		boolean dance() {
			if (R[0] == 0 || R[0] > bound)			
				return true;			
			int c = R[0];
			for (int i = R[0]; i != 0; i = R[i])
				if (S[i] < S[c] && i <= bound)
					c = i;
			for (int i = D[c]; i != c; i = D[i]) {
				remove(i);
				int idx = -1;
				for (int j = R[i]; j != i; j = R[j]) {
					if (C[j] <= bound)
						remove(j);
					else
						idx = j;
				}
				if (idx != -1)
					ExRemove(C[idx]); // C[]!!
				if (dance())
					return true;
				if (idx != -1)
					ExResume(C[idx]); // C[]!!
				for (int j = L[i]; j != i; j = L[j])
					if (C[j] <= bound)
						resume(j);
				resume(i);
			}
			return false;
		}
		int bound;
		void solve(int b) {
			this.bound = b;
			if (dance())
				System.out.println("Yes");
			else
				System.out.println("No");
		}
	}
	DLX dlx=new DLX();
	Scanner scan=new Scanner(System.in);
	int cnf[][]=new int[110][210],len[]=new int[110];
	void run()
	{
		int n=scan.nextInt();
		int m=scan.nextInt();
		for(int i=1;i<=n;i++){
			cnf[i][0]=cnf[i+n][0]=m+i;
			len[i]=len[i+n]=1;
		}
		dlx.init(m+n);
		for(int i=1;i<=m;i++){
			for(int j=0;j<3;j++){
				int v=scan.nextInt();
				if(v<0)
					v=n-v;
				cnf[v][len[v]++]=i;
			}
		}
		for(int i=1;i<=n*2;i++)
			dlx.insert(cnf[i], len[i]);
		dlx.solve(m);
	}
	public static void main(String[] args) {
		new SAT_3().run();
	}
}



你可能感兴趣的:(DLX解决3-SAT问题)