2-SAT问题
摘自:http://www.cppblog.com/Yuan/archive/2010/08/04/122209.html2 – SAT 就是2判定性问题,是一种特殊的逻辑判定问题。
例,n对东西,每对只能选一个(i0或i1),不能不选。即:A or _A = 1 , A xor _A = 1,还存在一些约束关系(i0,j0),表示i0不能跟j0一起选。那需连边
i0-> j1 如果选i0的话必须选j1
j0-> i1如果选j0的话必须选i1
表示了一种递推的关系:选哪个必选哪一个
一般题目给的都是一对一对的东西,二选一,不能不选。本身那几对东西是没有关系的,然后题目会定义一些规则,或者我们自己加入条件(如二分的参数),使对与对之间有了一些约束关系: 1)选a必选b a->b
2)a必选 _a->a
对这个新图求SCC,同一SCC的要么全选,要么都不选。
如果发现a,_a在同一SCC,表明矛盾了。
不矛盾的话,对缩点后的图按照自底向上选择一个还未被标记的点,标记选上,然后把它的对立点及其前代都删除。
(选择一个点要把它及其所有后代都选上。不选一个点,要把它及其前代都不选。)
算法流程:
1、建图,求SCC。若某一个点,a与_a在同一个SCC,则无解,退出;
2、对求得的缩点,用原来的反图建一个DAG;
3、TopoSort得到一个原图的自底向上的序列,点是缩点后的SCC,没必要原来的每个点;
4、从左往右,对整个序列的点,如果未标记删除,就标记选它,同时把这个SCC里的所有原来的点的对立点及其各自的后代(因为是反图)都标记删除;
5、重复上面过程,最后被标记选上的SCC内的点都是选择的点。其个数=n
虽然被删除的点也是n个,但是他们可能会存在冲突关系。而标记选上的点间是相容的。
题意:中文原题TSO:有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席。在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时出现在聚会上的。有没有可能会有n 个人同时列席?
思路:2-SAT判定问题,找强联通分量以后判定是否存在一对夫妻属于同一个强联通分量,有则不可能,输出"NO",否则输出"YES"。
#include <iostream>
#include <vector>
#include <stack>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 3009;
vector<int> g[MAXN] , gt[MAXN] , tree[MAXN]; // g正图 , gt 逆图 , tree缩点逆图
int n , m , cnt , ID , id[MAXN] , tree_cnt , order[MAXN] , indegree[MAXN] , color[MAXN];
bool used[MAXN] , used_tree[MAXN][MAXN];
void init()
{
for(int i=0; i < 2*n; i++) {
g[i].clear();
gt[i].clear();
}
}
void build_graph()// 建图,i << 1 与 (i << 1) + 1 相斥
{
int u , v , uu , vv , c1 , c2;
for(int i=0; i < m; i++){
scanf("%d%d%d%d",&u,&v,&c1,&c2);
if(c1 == 0){
u = u * 2;
uu = u + 1;
}
else{
u = 2 * u + 1;
uu = u - 1;
}
if(c2 == 0){
v = 2 * v;
vv = v + 1;
}
else{
v = 2 * v + 1;
vv = v - 1;
}
g[u].push_back( vv );
gt[vv].push_back( u );
g[v].push_back( uu );
gt[uu].push_back( v );
}
}
void dfs1( int u )
{
vector<int>::iterator v;
used[u] = true;
for(v = g[u].begin(); v != g[u].end(); v++)
if(!used[*v])
dfs1(*v);
order[cnt++] = u;
}
void dfs2( int u )
{
vector<int>::iterator v;
used[u] = true;
id[u] = ID;
for(v = gt[u].begin(); v != gt[u].end(); v++)
if(!used[*v])
dfs2(*v);
}
void scc() // 缩点
{
memset(used , 0 , sizeof(used));
cnt = 0;
for(int i=0; i < 2*n; i++)
if(!used[i])
dfs1(i);
memset(used , 0 , sizeof(used));
ID = 0;
for(int i=cnt - 1; i >= 0 ; i--)
if(!used[ order[i] ]){
ID ++;
dfs2( order[i] );
}
}
bool sat_judge() //2-sat判定
{
for(int i=0; i < 2*n; i+=2)
if(id[i] == id[i+1])
return false; //无解
return true;
}
void solve()
{
build_graph();
scc();
if(!sat_judge()) puts("NO");
else puts("YES");
return;
}
int main()
{
while(scanf("%d %d" , &n , &m ) != EOF) {
if(n == 0 && m == 0) break;
init();
solve();
}
return 0;
}
POJ 3683 Priest John's Busiest Day
题意:神父要在一天中给n对新人举行某仪式,给出每对新人允许的时间段和仪式所花费的时间,神父可以在这个时间段头或尾为新人举行仪式。问如果神父可以安排时间完成所有仪式则输出YES,并给出一组可行方案,否则输出NO。
解法:判定每一对新人的允许时间段,有重叠部分则冲突,用2-SAT模型解。
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
using namespace std;
const int MAXN = 3001;
vector<int> g[MAXN] , gt[MAXN] , tree[MAXN]; // g正图 , gt 逆图 , tree缩点逆图
int n , m , cnt , ID , id[MAXN] , tree_cnt , order[MAXN] , indegree[MAXN] , color[MAXN];
bool used[MAXN] , used_tree[MAXN][MAXN];
int t[MAXN][2];
void init()
{
for(int i=0; i < 2*n; i++) {
g[i].clear();
gt[i].clear();
}
return;
}
void build_graph()// 建图,i << 1 与 (i << 1) + 1 相斥
{
int a,b,c,d,e;
for(int i=1;i<=n;i++){
scanf("%d:%d %d:%d %d",&a,&b,&c,&d,&e);
t[2*i-2][0]=a*60+b;t[2*i-2][1]=a*60+b+e;
t[2*i-1][0]=c*60+d-e;t[2*i-1][1]=c*60+d;
}
for(int i=0;i<2*n;i++)
for(int j=i+1;j<2*n;j++){
if(i%2==0 && j==i+1) continue;
if(!(t[i][0]>=t[j][1] || t[j][0]>=t[i][1])){
g[i].push_back( j^1 );
gt[j^1].push_back( i );
g[j].push_back( i^1 );
gt[i^1].push_back( j );
}
}
}
void dfs1( int u )
{
vector<int>::iterator v;
used[u] = true;
for(v = g[u].begin(); v != g[u].end(); v++)
if(!used[*v])
dfs1(*v);
order[cnt++] = u;
}
void dfs2( int u )
{
vector<int>::iterator v;
used[u] = true;
id[u] = ID;
for(v = gt[u].begin(); v != gt[u].end(); v++)
if(!used[*v])
dfs2(*v);
}
void scc() // 缩点
{
memset(used , 0 , sizeof(used));
cnt = 0;
for(int i=0; i < 2*n; i++)
if(!used[i])
dfs1(i);
memset(used , 0 , sizeof(used));
ID = 0;
for(int i=cnt - 1; i >= 0 ; i--)
if(!used[ order[i] ]){
ID ++;
dfs2( order[i] );
}
}
bool sat_judge() //2-sat判定
{
for(int i=0; i < 2*n; i+=2)
if(id[i] == id[i+1])
return false; //无解
return true;
}
void build_tree() //缩点成树 , 建立逆图
{
memset(used_tree , 0 , sizeof(used_tree));
memset(indegree , 0 , sizeof(indegree));
for(int i=1; i <= ID; i++) tree[i].clear();
for(int i=0; i < 2*n; i++)
for(vector<int>::iterator j=g[i].begin(); j != g[i].end(); j++)
if(!used_tree[ id[*j] ][ id[i] ] && id[*j] != id[i]) {
indegree[ id[i] ] ++;
tree[ id[*j] ].push_back( id[i] );
used_tree[ id[*j] ][ id[i] ] = true;
}
}
void topsort()//拓扑排序
{
stack<int> S;
for(int i=1; i <= ID; i++)
if(indegree[i] == 0)
S.push( i );
tree_cnt = 0;
while( !S.empty() ) {
int u = S.top();
S.pop();
order[ tree_cnt ++ ] = u; //记录反向拓扑排序的序列
vector<int>::iterator v;
for(v = tree[u].begin(); v != tree[u].end(); v++) {
indegree[*v] --;
if(indegree[*v] == 0)
S.push( *v );
}
}
}
void dfs_tree( int u )
{
vector<int>::iterator v;
color[u] = 2;
for(v = tree[u].begin(); v != tree[u].end(); v++)
if(!color[*v])
dfs_tree(*v);
}
void tree_color()//对树进行染色
{
memset(color , 0 , sizeof(color));
for(int i=0; i < tree_cnt; i++)
if(!color[ order[i] ]){
color[ order[i] ] = 1;
for(int j=0; j < 2*n; j++) {
if(id[j] == order[i]) {
int tmp ;
if(j & 1) tmp = j - 1;
else tmp = j + 1;
tmp = id[ tmp ];
if(color[tmp]) continue;
color[tmp] = 2;
dfs_tree( tmp );//对前向边染色
}
}
}
}
void answer()
{
int sign = color[ id[0] ]; //得出结果
for(int i=0; i < n*2; i++){
if(color[id[i]] == sign){
int h1,h2,m1,m2;
h1=t[i][0]/60;m1=t[i][0]%60;
h2=t[i][1]/60;m2=t[i][1]%60;
if(h1<10)printf("0%d:",h1);
else printf("%d:",h1);
if(m1<10)printf("0%d ",m1);
else printf("%d ",m1);
if(h2<10)printf("0%d:",h2);
else printf("%d:",h2);
if(m2<10) printf("0%d\n",m2);
else printf("%d\n",m2);
}
}
return ;
}
void solve()
{
build_graph();
scc();
if(!sat_judge()){
printf("NO\n");
return;
}
printf("YES\n");
build_tree();
topsort();
tree_color();
answer();
}
int main()
{
freopen("in","r",stdin);
freopen("out","w",stdout);
while(EOF!=scanf("%d",&n)){
if(n==0) break;
init();
solve();
}
return 0;
}