一场选举中,每个选民有两条意见(或关系),判断是否存在一个选举方案不违背所有选民的意见。
2-SAT问题,具体的做法:每个选民的两个意见a,b之间为或关系,因此可以推出(!a,b)或者(!b,a),这样就可以使用有向线段连接!a到b,!b到a。将所有的意见用有向图表示后,使用tarjan算法求强连通子树,最后判断同一个子树中是否存在违背的意见。
代码块加上了自己的理解,注释。
#include <iostream>
#include <cstring>
#include <cstdio>
#include<cmath>
using namespace std;
#define N 2500
//定义节点,v是有向弧的入节点,next记录出节点上一条有向弧索引(相当于链表的作用)
struct node
{
int v;
int next;
}g[N*1000];
//head数组存储节点i对应最新的有向弧索引
int head[N],edgenum;
//add函数添加有向弧
void add(int u,int v)
{
g[edgenum].v = v;
g[edgenum].next = head[u];
head[u] = edgenum ++;
}
//为tarjan算法创建的数据结构
int dfn[N],low[N],nTime;
int belong[N],num;
int stack[N],ins[N],top;
int n,m;
//tarjan算法求强连通子树
void Tarjan(int u)
{
//dfn记录节点遍历的次序,low记录节点可以回溯到最先遍历节点的次序
dfn[u] = low[u] = ++nTime;
//节点入栈
stack[top++] = u;
//设置flag位
ins[u] = 1;
//dfs搜索
for( int i = head[u] ; i != -1 ; i = g[i].next )
{
int v = g[i].v;
if( !dfn[v] )
{
Tarjan(v);
//把当前节点的low设置为子节点可以连通的最先遍历的节点次序
low[u] = min(low[u],low[v]);
}
else if( ins[v] ) //判断flag位,因为已遍历的节点可能属于其他强连通子树(去除这种情况),后面会把flag位设置为0
//把当前节点的low
low[u] = min(low[u],dfn[v]);
}
//dfs与low相等即找到强连通子树,把同一子树的节点出栈
if(dfn[u] == low[u])
{
int k = 0;
num ++;
do
{
k = stack[--top];
belong[k] = num;
ins[k] =0;
}while( k != u );
}
}
int main()
{
int s1,s2;
while(scanf("%d%d",&n,&m)!=EOF)
{
nTime = num = edgenum = 0;
for( int i = 0 ; i <= 2*n ; i ++ )
{
head[i] = -1;
dfn[i] = 0;
belong[i] = 0;
ins[i] = 0;
}
// int u,v;
for(int i=0;i < m;i++)
{
scanf("%d%d",&s1,&s2);
int a=abs(s1);
int b=abs(s2);
if(s1>0 && s2>0)
{
add(a+n,b);
add(b+n,a);
}
if(s1<0 && s2<0)
{
add(a,b+n);
add(b,a+n);
}
if(s1>0 && s2<0)
{
add(a+n,b+n);
add(b,a);
}
if(s1<0 && s2>0)
{
add(a,b);
add(b+n,a+n);
}
}
for( int i = 1 ; i <= n+n ; i ++ )
{
if( !dfn[i] )
Tarjan(i);
}
bool flag = true;
for( int i = 1 ; i <= n ; i ++ )
{
if( belong[i] == belong[i+n] )
{
flag = false;
break;
}
}
if( flag )
printf("1\n");
else
printf("0\n");
}
return 0;
}
参考:
1.tarjan算法:https://blog.csdn.net/jeryjeryjery/article/details/52829142
2.2-sat模板:https://blog.csdn.net/u011453159/article/details/16842139