2-SAT问题

概念:

确定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");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值