POJ3648-2SAT解的求得

算法参考 : 2-SAT 解法浅析 华中师大一附中 赵爽

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int NN = 100;
const int MM = 1000;

struct TwoSAT {
	int n, en, head[NN], next[MM], to[MM], from[MM]; // 原图(前向星), n个点, 2SAT编号0 ~ 2*n-1, x与x^1表示一个点的两个对立sat

	bool inS[NN];
	stack stk;
	int bcnt, vcnt, dfn[NN], low[NN], belong[NN]; // tarjan相关, 各计数/编号都是从0开始
	
	int in[NN], color[NN], op[NN];
	vector e[NN]; // 具体求解相关, e为tarjan缩点后建的反图, 用不同的数据结构容易区分~~

	void init(int _n) {
	// 初始化
		n = _n;
	    en = 0;
	    for (int i = 0; i < 2 * n; i++) head[i] = -1;
	}

	void addEdge(int u, int v) {
	// 加单向边
		from[en] = u; to[en] = v; next[en] = head[u]; head[u] = en++;
	}

	void tarjan(int u) {
	    dfn[u] = low[u] = vcnt ++;
	    stk.push(u); inS[u] = true;

	    int v;
	    for (int i = head[u]; i != -1; i = next[i]) {
	    	v = to[i];
	        if (dfn[v] == -1) {
	            tarjan(v);
	            low[u] = min(low[u], low[v]);
	        } else if (inS[v]) {
	            low[u] = min(low[u], dfn[v]);
	        }
	    }

	    if (low[u] == dfn[u]) {
	        bcnt ++;
	        do {
	            v = stk.top(); stk.pop();
	            inS[v] = false;
	            belong[v] = bcnt - 1;
	        } while (v != u);
	    }
	}

	bool check() {
	// 判断2SAT是否有解
	    vcnt = bcnt = 0;
	    while (!stk.empty()) stk.pop();
	    for (int i = 0; i < 2 * n; i++) { inS[i] = false; dfn[i] = -1; }
	    for (int i = 0; i < 2 * n; i++) if (dfn[i] == -1) tarjan(i);
	    for (int i = 0; i < 2 * n; i += 2) if (belong[i] == belong[i ^ 1]) return false;
	    return true;
	}

	vector solve() {
	// 求解2SAT, 返回值为一个长度为n, 元素内容为0或1, 0代表x为解, 1代表x^1为解
	    for (int i = 0; i < bcnt; i++) {
	        e[i].clear(); // 如果数据有多组, 点很多, 边很多, 建议从0clear到NN-1为止, 其它vector变量也是如此
	        in[i] = 0;
	    }

	    for (int i = 0; i < 2 * n; i += 2) {
	    // * 图的边是'对称'的, 因此, 一个浓缩点的对立点应该只有一个
	    	op[belong[i]] = belong[i ^ 1];
	    	op[belong[i ^ 1]] = belong[i];
	    }

	    for (int i = 0; i < en; i++) {
	    // 建缩点后的反图
	        int u = belong[from[i]];
	        int v = belong[to[i]];
	        if (u == v) continue;
	        e[v].push_back(u);
	        in[u]++;
	    }

	    queue q;
	    for (int i = 0; i< bcnt; i++) {
	        color[i] = -1; // 染色法, -1表示没有染色, 1表示红色(选), 0表示蓝色(不选)
	        if (!in[i]) q.push(i); // 反图拓扑序染色
	    }

	    while (!q.empty()) {
	        int u = q.front(); q.pop();

	        if (color[u] == -1) { // 如果当前点没有着色
	            color[u] = 1; // 则染红色
	            color[op[u]] = 0; // 其对立点染蓝色(及其子孙后继结点也要染成蓝色)
	        }

	        for (int i = 0; i<(int)e[u].size(); i++) {
	            int v = e[u][i];
	            if (!color[u]) color[v] = 0; // 如果当前点是蓝色, 则其子孙都要染蓝色
	            if (--in[v] == 0) q.push(v);
	        }
	    }

	    vector ans; // 返回值为vector, 恩, 可能不是很好, 自行更改兼容
	    for (int i = 1; i < 2 * n; i += 2) { // i = 1开始, 别抄错了
	    	ans.push_back(color[belong[i]]);
	    }
	    return ans;
	}
} t;

int main() {
	int x1, x2, n, m;
    char c1, c2;
    while (scanf("%d%d", &n, &m), n | m) {
        t.init(n);
        t.addEdge(0, 1);
        for (int i = 1; i <= m; i++) {
            scanf("%d%c %d%c", &x1, &c1, &x2, &c2);
            x1 <<= 1;
            x2 <<= 1;
            if (c1 == 'h') x1++;
            if (c2 == 'h') x2++;
            if (x1 == (x2 ^ 1)) continue;
            t.addEdge(x1, x2 ^ 1);
            t.addEdge(x2, x1 ^ 1);
        }

        if (!t.check()) printf("bad luck\n");
        else {
        	vector ans = t.solve();
      		for (int i = 1; i < n; i++) {
      			printf("%d%c%c", i, !ans[i] ? 'h' : 'w', i == n - 1 ? '\n' : ' ');
      		}
        }
    }
    return 0;
}


你可能感兴趣的:(图论,ACM)