2-sat 【模板】

好烦。写一道题 TLE25次,感觉已经尽力了。。。留下来,以后做。

 

正好趁现在补下2-sat模板。前几天做2-sat,就一个有向图tarjan算法就敲了不下20遍,对这个算法理解的更加透彻了,而且还间接帮我理解了二分查找,收获不少。

 

2-sat 模板(对于DFS 暴力求最小字典序的模板,我就不写了,网上这个模板比较多)

 

一:判断是否存在可行解,若存在输出任意一组可行解。

 

思路:根据相对关系建边,先tarjan求SCC,判断相对关系的变量是否在同一个SCC里面。若所有变量均满足它和它相对变量不在同一个SCC里面,就说明存在可行解,反之不存在。判断后 ,就是SCC缩点 + 反向建边,然后拓扑排序 + 染色,染成同一种颜色就是一组可行解。

 

模板:

#include 
#include 
#include 
#include 
#include 
#include 
#define MAXN 
#define MAXM //根据题目需求 
#define eps 1e-5
using namespace std;
struct Edge
{
	int from, to, next;
}edge[MAXM];//原图
int head[MAXN], edgenum; 
vector G[MAXN];//缩点后新图 
int in[MAXN];//新图SCC的入度 
int low[MAXN], dfn[MAXN];//通过子女节点所能到达的最低深度优先数  深度优先数 
int dfs_clock;//时间戳 
int sccno[MAXN], scc_cnt;//sccno[i]表示i属于哪个SCC  scc_cnt是SCC计数器 
stack S;//存储当前SCC的点 
bool Instack[MAXN];//判断点是否在栈里面 
int color[MAXN];//染色 
int fp[MAXN];//建立对应SCC编号的映射 
void init()
{
	edgenum = 0;
	memset(head, -1, sizeof(head)); 
}
void addEdge(int u, int v)
{
	Edge E = {u, v, head[u]};
	edge[edgenum] = E;
	head[u] = edgenum++;
} 
void getMap()//建图
{
	
} 
void tarjan(int u, int fa)//求SCC 
{
	int v;
	low[u] = dfn[u] = ++dfs_clock;
	S.push(u);
	Instack[u] = true;
	for(int i = head[u]; i != -1; i = edge[i].next)
	{
		v = edge[i].to;
		if(!dfn[v])
		{
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
		}
		else if(Instack[v])
		low[u] = min(low[u], dfn[v]);
	}
	if(low[u] == dfn[u])
	{
		scc_cnt++;
		for(;;)
		{
			v = S.top(); S.pop();
			Instack[v] = false;
			sccno[v] = scc_cnt;
			if(v == u) break;
		}
	}
}
void find_cut(int l, int r)
{
	memset(low, 0, sizeof(low));
	memset(dfn, 0, sizeof(dfn));
	memset(sccno, 0, sizeof(sccno));
	memset(Instack, false, sizeof(Instack));
	dfs_clock = scc_cnt = 0;
	for(int i = l; i <= r; i++)
	if(!dfn[i]) tarjan(i, -1);
}
void suodian()//缩点 + 反向建图 
{
	for(int i = 1; i <= scc_cnt; i++) G[i].clear(), in[i] = 0;
	for(int i = 0; i < edgenum; i++)
	{
		int u = sccno[edge[i].from];
		int v = sccno[edge[i].to];
		if(u != v)
		G[v].push_back(u), in[u]++; //反向建图 
	} 
}
void toposort()//拓扑排序 + 染色 
{
	queue Q;
	memset(color, 0, sizeof(color));
	for(int i = 1; i <= scc_cnt; i++) if(in[i] == 0) Q.push(i);
	while(!Q.empty())
	{
		int u = Q.front();
		Q.pop();
		if(color[u] == 0)//没有颜色 
		{
			color[u] = 1;//染色 
			color[fp[u]] = 2;
		}
		for(int i = 0; i < G[u].size(); i++)
		{
			int v = G[u][i];
			if(--in[v] == 0)//入度减一 
			Q.push(v);
		}
	}
}
void solve()
{
	memset(fp, 0, sizeof(fp));
	for(int i = 0; i < N; i++)
	{
		if(sccno[2*i] == sccno[2*i + 1])
		{
			printf("无可行解\n");
			return ;
		}
		else
		{
			fp[sccno[2*i]] = sccno[2*i + 1];
			fp[sccno[2*i + 1]] = sccno[2*i];
		}
	}
	suodian();//缩点 
	toposort();//拓扑排序 + 染色 
	printf("一组可行解:\n");
	for(int i = 1; i < N; i++)
	{
		if(color[sccno[2*i]] == 1)//可行解 
		printf("");
	}
	printf("\n");
}
int main()
{
	while("题目输入")
	{
		init();//初始化 
		getMap();//建图 
		find_cut(1, 2*N-1);//找SCC 
		solve();//判断是否存在可行解 或者 输出一组可行解 
	}
	return 0;
}


 

二:在区间[a, b]寻找使得2-sat成立的最优解

 

思路:基于二分查找

 

二分代码如下:

1,整数解

void solve()//二分查找 
{
	int left, right, mid;
	int ans;//最后结果 
	left = a, right = b;
	while(right >= left)
	{
		mid = (left + right) / 2;
		init();
		getMap(mid);//根据当前值建图 
		find_cut(0, 2*N-1);//找SCC 
		if(two_sat())//判断当前是否可行 
		{
			ans = mid;//更新 
			left = mid + 1;
		}
		else
		right = mid - 1;
	}
	printf("%d\n", ans);
}


 

2,浮点数解:

 

#define eps //根据需求设置精度 
void solve()//二分查找 
{
	double left, right, mid;
	left = a, right = b;
	while(right - left > eps)
	{
		mid = (left + right) / 2;
		init();
		getMap(mid);//根据当前值建图 
		find_cut(0, 2*N-1);//找SCC 
		if(two_sat())//判断当前是否可行 
		left = mid; 
		else
		right = mid;
	}
	printf("%.3lf\n", left);//输出right 和 left 都可以 
}


 

 

 

你可能感兴趣的:(算法与有趣代码--记录,2-sat)