好博客链接:http://www.cnblogs.com/ambition/archive/2011/07/30/2-sat.html(内有ppt讲解、题集、论文)
建图如上篇一样,主要不同是上篇利用暴力来求,但上篇可以保证按字典序最小进行输出,此处利用了比较好的算法,产生了o(m)的复杂度,应用于所有题,主要思路是利用了图的对称的原理,根据建图可知,若存在强连通图,则其中选择了一个,其他点也必须进行选择,因为存在方向性,强连通图中要求一个点能到达其中的任意一点,必全选,这就包括若两个不能同时选择的一对若都在此连通环图中则该题必定无解,若不存在这样的情况,则此题有解,证明见论文,所以证明是否有解的快速方法就是建图后求联通分量,然后进行判断,方法极快,连通性利用Tarjan算法,然后枚举i和i^1是否同在一个环内即可,如在一个环内就是无解,不在同一环就是有解
题目:http://acm.hdu.edu.cn/showproblem.php?pid=3062
题意:有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席。在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时出现在聚会上的。有没有可能会有n 个人同时列席?
输入说明:n: 表示有n对夫妻被邀请 (n<= 1000)
m: 表示有m 对矛盾关系 ( m < (n - 1) * (n -1))
在接下来的m行中,每行会有4个数字,分别是 A1,A2,C1,C2
A1,A2分别表示是夫妻的编号
C1,C2 表示是妻子还是丈夫 ,0表示妻子 ,1是丈夫
夫妻编号从 0 到 n -1
题解:Tarjan算法
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
//以下模板
const int MAX=5555;
int dfn[MAX],low[MAX],belong[MAX],time;
int cont;
bool instack[MAX];
int _stack[MAX],top;
vector<int> dian[MAX];
void tarjan(int root)
{
dfn[root]=low[root]=time++;
_stack[top++]=root;
instack[root]=true;
for(int i=0;i<dian[root].size();i++)
{
int son=dian[root][i];
if(!dfn[son])
{
tarjan(son);
low[root]=min(low[root],low[son]);
}
else if(instack[son])
{
low[root]=min(low[root],dfn[son]);
}
}
if(dfn[root] == low[root])
{
int v;
do
{
v=_stack[--top];
instack[v]=false;
belong[v]=cont;
}
while(root!=v);
cont++;
}
}
void init_tarjan(int n)
{
memset(dfn,0,sizeof(dfn));
memset(instack,false,sizeof(instack));
top=time=cont=1;
for(int i=0;i<n;i++)
{
if(dfn[i]==0) tarjan(i);
}
}
//以上模板
int main()
{
int n,m;
while(~scanf("%d",&n))
{
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int s,e,d1,d2;
scanf("%d %d %d %d",&s,&e,&d1,&d2);
dian[(s<<1)+d1].push_back((e<<1)+(d2+1)%2);
dian[(e<<1)+d2].push_back((s<<1)+(d1+1)%2);
}
init_tarjan(2*n);
bool flag=true;
for(int i=0;i<n;i++)
{
if(belong[i<<1]==belong[(i<<1)+1]) {flag=false;}
dian[i<<1|1].clear();dian[i<<1].clear();
}
if(flag) puts("YES");
else puts("NO");
}
return 0;
}
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1824
题意:队伍放假,每个队伍3个人,队长或其他两个人放假,不能同时放假,队间有人存在相互关系
输入说明:第一行有两个整数,T和M,1<=T<=1000表示队伍数,1<=M<=5000表示对数。
接下来有T行,每行三个整数,表示一个队的队员编号,第一个队员就是该队队长。
然后有M行,每行两个整数,表示一对队员的编号。
每个队员只属于一个队。队员编号从0开始。
题解:同上
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int MAX=5555;
int dfn[MAX],low[MAX],belong[MAX],time;
map<int,int> mp;
int cont;
bool instack[MAX];
int _stack[MAX],top;
vector<int> dian[MAX];
void tarjan(int root)
{
dfn[root]=low[root]=time++;
_stack[top++]=root;
instack[root]=true;
for(int i=0;i<dian[root].size();i++)
{
int son=dian[root][i];
if(!dfn[son])
{
tarjan(son);
low[root]=min(low[root],low[son]);
}
else if(instack[son])
{
low[root]=min(low[root],dfn[son]);
}
}
if(dfn[root] == low[root])
{
int v;
do
{
v=_stack[--top];
instack[v]=false;
belong[v]=cont;
}
while(root!=v);
cont++;
}
}
void init_tarjan(int n)
{
memset(dfn,0,sizeof(dfn));
memset(instack,false,sizeof(instack));
top=time=cont=1;
for(int i=0;i<n;i++)
{
if(dfn[i]==0) tarjan(i);
}
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
int cnt=0;
for(int i=1;i<=n;i++)
{
int s,w,t;
scanf("%d %d %d",&s,&w,&t);
mp[s]=cnt++;
mp[w]=cnt;
mp[t]=cnt++;
}
for(int i=1;i<=m;i++)
{
int s,e;
scanf("%d %d",&s,&e);
dian[mp[s]].push_back(mp[e]^1);
dian[mp[e]].push_back(mp[s]^1);
}
init_tarjan(cnt);
bool flag=true;
for(int i=0;i<cnt;i+=2)
{
if(belong[i]==belong[i^1]) {flag=false;}
dian[i].clear();dian[i^1].clear();
}
mp.clear();
if(flag) puts("yes");
else puts("no");
}
return 0;
}