题目:
有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席。在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时出现在聚会上的。有没有可能会有n 个人同时列席?
Input
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
Output
如果存在一种情况 则输出YES
否则输出 NO
Sample Input
2
1
0 1 1 1
Sample Output
YES
分析
2-sat详解:https://blog.csdn.net/zeng_jun_yv/article/details/105316871
一道2—SAT的模板题,其实就是建立一个无向图,然后求强连通分量,再看看同一对夫妻是不是在同一个强连通分量里。由对称性解2-SAT问题
AC代码:
/*由对称性解2-SAT问题*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
#include<algorithm>
using namespace std;
const int N = 2010;
vector<int> vec[N];
int n, m, id, cnt;
int dfn[N], vis[N], low[N], belong[N];
stack<int> s;
void init()
{memset(vis,0,sizeof(vis));memset(dfn,-1,sizeof(dfn));memset(low,-1,sizeof(low));memset(belong,-1,sizeof(belong));id = cnt = 0;while(!s.empty())s.pop();for(int i = 0; i < 2*n; i++)vec[i].clear();
}
void tarjan(int u)
{dfn[u] = low[u] = id++;vis[u] = 1;int sz = vec[u].size();s.push(u);for(int i = 0; i < sz; i++){int v = vec[u][i];if(dfn[v] == -1){tarjan(v);low[u] = min(low[u], low[v]);}else if(vis[v] == 1){low[u] = min(low[u], dfn[v]);}}if(low[u] == dfn[u]){cnt++;while(!s.empty()){int temp = s.top();s.pop();vis[temp]=0;belong[temp]=cnt;if(temp == u)break;}}
}
int main()
{int a,b,c,d;while(~scanf("%d%d", &n, &m)){init();for(int i = 0; i < m; i++){scanf("%d%d%d%d", &a, &b, &c, &d);vec[2*a+c].push_back(2*b+1-d);vec[2*b+d].push_back(2*a+1-c);}for(int i = 0; i < 2*n; i++){if(dfn[i] == -1)tarjan(i);}bool flag = true;for(int i = 0; i < n; i++){if( belong[2*i] == belong[2*i+1] ){flag=false;break;}}puts(flag?"YES":"NO");}return 0;
}
Tarjan+缩点+拓扑+染色
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std ;
#define M 4000017
#define N 100017
//a<<1 和 (a<<1) + 1。a<<1表示妻子,(a<<1) + 1表示丈夫
//连接某边是为了推出矛盾。x->y表示选x则必须选y
//注意方向
struct node
{int s, t;int nxt;
} e[M];
int n, m;
int idx, ans, tp, cont;
int dfn[N],vis[N],low[N],head[N],st[N],belong[N];void add(int s,int t)
{e[cont].s = s;e[cont].t = t;e[cont].nxt = head[s];head[s] = cont++;
}
void build_grap(int a, int b, int c, int d)//建图
{if(c==0 && d==0)//两个妻子{add(a<<1, (b<<1)+1);add(b<<1, (a<<1) + 1);}else if(c==0 && d==1)//妻子和丈夫{add((a<<1), (b<<1));add((b<<1)+1, (a<<1)+1);}else if(c==1 && d==0)//丈夫和妻子{add((a<<1) + 1, (b<<1) + 1);add(b<<1, a<<1);}else if(c==1 && d==1)//两个丈夫有矛盾{add((a<<1)+1, b<<1);add((b<<1)+1, a<<1);}
}
void tarjan(int u)
{dfn[u] = low[u] = ++idx;vis[u] = 1;st[++tp] = u;int v ;for(int i = head[u]; i != -1; i = e[i].nxt){v = e[i].t ;if(!dfn[v]){tarjan(v) ;low[u] = min(low[u],low[v]);}else if(vis[v])low[u] = min(low[u],dfn[v]);}if(dfn[u] == low[u]){ans++;while(1){v = st[tp--];vis[v] = 0;belong[v] = ans;if(v == u)break;}}
}
bool _2sat()
{memset(vis,0,sizeof(vis));memset(dfn,0,sizeof(dfn));idx = tp = ans = 0;for(int i = 0; i < 2*n; i++)if(!dfn[i])tarjan(i) ;for(int i = 0; i < n; i++)if(belong[2*i]==belong[(2*i)^1])//矛盾return false ;return true;
}int main()
{int a, b, c, d;while(~scanf("%d%d",&n,&m)){cont = 0;memset(head,-1,sizeof(head));for(int i = 0; i < m; i++){scanf("%d%d%d%d",&a,&b,&c,&d);//int u, v;//u = a*2+c; v = b*2+d;//add(u, v^1); add(v, u^1);build_grap(a, b, c, d);}if(_2sat())printf("YES\n");elseprintf("NO\n");}return 0 ;
}
/**思路:每对夫妻代表图中一个结点,只有 1、0 两种选择,对于有矛盾的夫妻对,使其不列席,让无矛盾夫妻对的列席即可
对于 m 矛盾关系,设 a、b 两对夫妇存在矛盾:
若第 a 对的妻子与第 b 对的妻子有矛盾(a b 0 0)
则 a 的妻子去了 b 的丈夫必须去,b 的妻子去了 a 的丈夫必须去:<a,0,b,1>、<b,0,a,1>,添边:<a+n,b>,<b+n,a>
若第 a 对的妻子与第 b 对的丈夫有矛盾(a b 0 1)
则 a 的妻子去了 b 的妻子必须去,b 的丈夫去了 a 的丈夫必须去:<a,0,b,0>、<b,1,a,1>,添边:<a+n,b+n>,<b,a>
若第 a 对的丈夫与第 b 对的妻子有矛盾(a b 1 0)
则 a 的丈夫去了 b 的丈夫必须去,b 的妻子去了 a 的妻子必须去:<a,1,b,1>、<b,0,a,0,>,添边:<a,b>,<b+n,a+n>
若第 a 对的丈夫与第 b 对的丈夫有矛盾(a b 1 1)
则 a 的丈夫去了 b 的妻子必须去,b 的丈夫去了 a 的妻子必须去:<a,1,b,0>、<b,1,a,0>,添边:<a,b+n>,<b,a+n>*/
#include<iostream>
#include<cstdio>
#include<stack>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
const int dx[] = {-1,1,0,0,-1,-1,1,1};
const int dy[] = {0,0,-1,1,-1,1,-1,1};
using namespace std;
struct node
{int to,next;
} node[N*2];
int head[N],tot;
int n,m;
int dfn[N],low[N];
bool vis[N];//标记数组
int scc[N];//记录结点i属于哪个强连通分量
int block_cnt;//时间戳
int sig;//记录强连通分量个数
stack<int> S;
void init()
{tot=0;sig=0;block_cnt=0;memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));memset(scc,0,sizeof(scc));
}
void addnode(int from,int to)
{node[++tot].to=to;node[tot].next=head[from];head[from]=tot;
}
void Tarjan(int x)
{vis[x]=true;dfn[x]=low[x]=++block_cnt;//每找到一个新点,纪录当前节点的时间戳S.push(x);//当前结点入栈for(int i=head[x]; i!=-1; i=node[i].next) //遍历整个栈{int y=node[i].to;//当前结点的下一结点if(!dfn[y]){Tarjan(y);low[x]=min(low[x],low[y]);}else if(vis[y])low[x]=min(low[x],dfn[y]);}if(dfn[x]==low[x]) //满足强连通分量要求{sig++;//记录强连通分量个数while(true) //记录元素属于第几个强连通分量{int temp=S.top();S.pop();vis[temp]=false;scc[temp]=sig;if(temp==x)break;}}
}
bool twoSAT()
{for(int i=1; i<=2*n; i++) //找强连通分量if(!dfn[i])Tarjan(i);for(int i=1; i<=n; i++)if(scc[i]==scc[i+n])//条件a与!a属于同一连通分量,无解return false;return true;
}
int main()
{while( scanf("%d%d",&n,&m)!=EOF&&(n+m)){init();while(m--){int x,y,xVal,yVal;scanf("%d%d%d%d",&x,&y,&xVal,&yVal);x++;y++;if(xVal==0&&yVal==0) //x为0或y为0{addnode(x+n,y);//x为0,y为1addnode(y+n,x);//y为0,x为1}else if(xVal==0&&yVal==1) //x为0或y为1{addnode(x+n,y+n);//x为0,y为0addnode(y,x);//y为1,x为1}else if(xVal==1&&yVal==0) //x为1或y为0{addnode(x,y);//x为1,y为1addnode(y+n,x+n);//y为0,x为0}else if(xVal==1&&yVal==1) //x为1或y为1{addnode(x,y+n);//x为1,y为0addnode(y,x+n);//y为1,x为0}}bool flag=twoSAT();if(!flag)printf("NO\n");elseprintf("YES\n");}return 0;
}