2-sat入门

来源:互联网 发布:大隈机械okuma编程 编辑:程序博客网 时间:2024/05/29 16:57

一、2-SAT 简介:

SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k-SAT。
当k>2时,k-SAT是NP完全的。因此一般讨论的是k=2的情况,即2-SAT问题。
2-SAT就是2判定性问题(条件只有一个,不是这个就是那个),是一种特殊的逻辑判定问题。

2-SAT,简单的说就是给出n个集合,每个集合有两个元素,已知若干个<a,b>,表示a与b矛盾(其中a与b属于不同的集合)。然后从每个集合选择一个元素,一共选n个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。

二、2-SAT 算法流程:

 1.构图   (重点+难点)
 2.求图的极大强连通子图 (模板)
 3.把每个子图收缩成单个节点,根据原图关系构造一个有向无环图 (模板)
 4.判断是否有解,无解则输出(退出) (这块常用到二分枚举答案)
 5.对新图进行拓扑排序 (模板)
 6.自底向上进行选择、删除 (模板)
 7.输出(模板)


构图,找题目所述的相互矛盾,使不矛盾的那个状态必连,形成有入度出度的有向图,然后求有向图的强连通分量,缩点,对缩点后的图进行拓扑排序,用逆向拓扑序列进行删选,这样就可以求出一种可行解。算法复杂度O(m)

求出强连通分量后只需判断一个点的两个状态,如果两种状态都在同一个连通分量里,则 无解,否则肯定有一可行解。


例题:Poi 0106 Peaceful Commission [和平委员会]
某国有n个党派,每个党派在议会中恰有2个代表。
现在要成立和平委员会 ,该会满足:
每个党派在和平委员会中有且只有一个代表 
如果某两个代表不和,则他们不能都属于委员会 
代表的编号从1到2n,编号为2a-1、2a的代表属于第a个党派

判断该和平委员会是否能够成立,给出一种可行解。

输入n(党派数),m(不友好对数)及m对两两不和的代表编号 
其中1≤n≤8000,0≤m ≤20000 
例:输入:3 2              输出:1
           1 3                    4
           2 4                    5

建图:有n个组,每个组里有两个人(a1,a2),另外一个组(b1,b2)如果a1敌对b1,则选择a1,b2或a2,b1

所以就是a1 --> b1建一条有向边,a2  -->  b1建一条有向边

具体的2-sat讲解见http://blog.csdn.net/zixiaqian/article/details/4492926

原题在HIT 1917

int n,m;vector<int>g[N],gt[N],mat[N],ord;int vis[N],belong[N],color[N],cnt;void init(){    for(int i = 1 ; i <= 2*n ; i ++)    {        gt[i].clear();        g[i].clear();        mat[i].clear();    }    while(m--)//对相互限制的两个人建图    {        int u,v;        scanf("%d %d",&u,&v);        u--,v--;        g[u+1].push_back((v^1) + 1);        g[v+1].push_back((u^1) + 1);        gt[(v^1) + 1].push_back(u+1);        gt[(u^1) + 1].push_back(v+1);    }}void dfs1(int u){    vis[u] = 1;    for(int i = 0 ;  i < g[u].size() ;  i++)        if(!vis[g[u][i]]) dfs1(g[u][i]);    ord.push_back(u);}void dfs2(int u){    vis[u] = 1;    belong[u] = cnt;    for(int i = 0 ; i < gt[u].size() ; i ++)        if(!vis[gt[u][i]]) dfs2(gt[u][i]);}void kosaraju()//求强连通分量{    memset(vis,0,sizeof(vis));    ord.clear();    for(int i = 1 ; i <= 2*n ; i ++)        if(!vis[i]) dfs1(i);    cnt = 0;    memset(vis,0,sizeof(vis));    for(int i =ord.size() - 1 ; i>=0 ; i--)        if(!vis[ord[i]])        {            cnt++;            dfs2(ord[i]);        }}void dfs(int u)//拓扑排序,注意这里可能拓扑序列不唯一,所以答案是多解的{    vis[u] = 1;    for(int i = 0 ; i < mat[u].size() ;  i++)        if(!vis[mat[u][i]]) dfs(mat[u][i]);    ord.push_back(u);}void solve(){    for(int i = 1 ; i <= n ; i ++)//判断误解状态        if(belong[2*i-1] == belong[2*i])            {                puts("NIE");                return;            }    for(int i = 1 ; i <= 2*n ; i ++)//缩点    {        for(int j = 0 ; j < g[i].size(); j ++)        {            int u = i,v = g[i][j];            if(belong[u] != belong[v])            {                mat[belong[u]].push_back(belong[v]);//缩点后的图mat            }        }    }    memset(vis,0,sizeof(vis));    memset(color,0,sizeof(color));    ord.clear();    for(int i = 1 ; i <= cnt ; i ++)//对缩点后的图进行拓扑排序        if(!vis[i]) dfs(i);    memset(vis,0,sizeof(vis));    for(int i = 1 ; i <= cnt ; i ++)        mat[i].clear();                    for(int i = 1 ; i <= 2*n ;  i ++)//将每个连通分量的节点放到一起,连通分量里的点要么全选要么都不选        mat[belong[i]].push_back(i);    for(int i = 0 ; i < ord.size() ; i ++)//逆向拓扑序列进行筛选    {        if(!vis[ord[i]])        {            int u = ord[i];            for(int j = 0 ; j < mat[u].size(); j ++)//mat[u][j]是连通分量ord[i]里面的点            {                int cur = ((mat[u][j]-1)^1) + 1;//找出与mat[u][j]是同一组的人                color[cur] = 1;//由于同一组只能选一个人,所以cur不能选了                vis[belong[cur]] = 1;//同一组的另一个人肯定在另外一个连通分量,标记这个连通分量就不选了            }        }    }    for(int i = 1 ; i <= 2*n ; i ++)//输出一组可行解        if(!color[i]) printf("%d\n",i);}


原创粉丝点击