概念:
确定N个布尔变量的值(每个单位只有可能出现两种情况),使得其满足所有的限制条件。
解决算法:
-
对于N个单位,建立2N个点的有向图,其中一个点代表该单位为真,另一个点代表该单位为假。
-
对于两点之间的边代表的限制关系分为一元关系和二元关系:(设xi为某个单元,i代表其为真,i'代表其为假)
一元:
xi=false,则建立i->i'的一条边,反之xi=true则建立i'->i的边
二元关系:
对于两个命题u和v,u代表真,u'代表假,v同理。
那么若u则v可表示为u->v且v'->u'建立两条边才严谨,一定要考虑到二元关系具有逆否命题这一易错点。
u or v=true:u'->v且v'->u
u or v=false:u->u'且v->v'
u and v=true:u<-u'且v<-v'
u and v=false:u'<-v且v'<-u
u!=v:u->v' and u'->v and v->u' and v'->u
u=v:u->v and u'->v' and v->u and v'->u'
-
因此对于问题有没有解,即可转化为i与i'是否可以相互到达,若可以则矛盾,说明问题无解。判断两点是否可以相互到达,即判断两点是否在同一强连通分量上,问题转化为在构造好的图上求解强连通分量。
-
如果有解,要求输出可行方案,则可在kosaraju算法中经过第一次dfs求出的trace数组上,从后往前遍历,对于同一个单元的两点,输出排名靠前的那个点即可。因为该数组满足拓扑序,数组后面的点拓扑序一定靠前,也就是说可能可以从数组后面的点推出数组前面的点,这时不妨输出数组前面的点避免矛盾。代码实现时,可使用SCC编号更靠后的点即可。
代码具体实现:
原题给定n个单位,可以将某单位x为真的点放入1~n的区域内,x为否的点放入n+1~2n的区域,这样方便建边。
也即i代表真,i+n代表否。
最后根据具体需求建边,千万不能漏边
#include<bits/stdc++.h>//洛谷P4782 【模板】2-SAT
#define INF 0x3f3f3f3f
#define maxn 100000
#define ll long long
using namespace std;
int n,m;
int head1[maxn],head2[maxn];
struct Edge{
int to;
int next;
}edge1[maxn],edge2[maxn];
int vis1[maxn],vis2[maxn];
int trace[maxn],belong[maxn];
int ans[maxn];
int cnt1=0,cnt2=0,tot1=0,tot2=0;
void addedge(int u,int v)
{
edge1[++tot1].to=v;
edge1[tot1].next=head1[u];
head1[u]=tot1;
edge2[++tot2].to=u;
edge2[tot2].next=head2[v];
head2[v]=tot2;
}
void dfs1(int p)
{
vis1[p]=1;
for(int i=head1[p];~i;i=edge1[i].next)
if(!vis1[edge1[i].to])
dfs1(edge1[i].to);
trace[++cnt1]=p;
}
void dfs2(int p)
{
vis2[p]=1;
belong[p]=cnt2;
for(int i=head2[p];~i;i=edge2[i].next)
if(!vis2[edge2[i].to])
dfs2(edge2[i].to);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<=2*n;i++)
head1[i]=head2[i]=-1;
for(int i=1;i<=m;i++)
{
int u,v,f1,f2;
scanf("%d%d%d%d",&u,&f1,&v,&f2);
addedge(u+n*(f1),v+n*(!f2));
addedge(v+n*(f2),u+n*(!f1));//建边技巧
}
memset(vis1,0,sizeof(vis1));
memset(vis2,0,sizeof(vis2));
for(int i=1;i<=2*n;i++)
if(!vis1[i])
dfs1(i);
for(int i=cnt1;i>=1;i--)
if(!vis2[trace[i]])
{
++cnt2;
dfs2(trace[i]);
}
//输出
int ifva=1;
for(int i=1;i<=n;i++)
if(belong[i]==belong[i+n])
{
ifva=0;
break;
}
else if(belong[i]>belong[i+n])
ans[i]=1;
else
ans[i]=0;
if(ifva)
{
printf("POSSIBLE\n");
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
}
else
printf("IMPOSSIBLE\n");
system("pause");
}