2-sat从入门到入门

来源:互联网 发布:win10软件都不见了 编辑:程序博客网 时间:2024/05/29 01:52

这两天沉迷于2-sat问题。

来个题目汇总,一般2-sat都会写这几题。

POJ 3207 

POJ 3683 

POJ 3678 

POJ 3648

POJ 2723      //啊啊啊啊没思路先放放

POJ 2749      //同上


参考资料:由对称性解2-sat问题

参考博客:博客


POJ 3207

题意:给在一个圈上的n个点,连m条线,然后判断可不可能都不相交,线可以在圈外连也可以在圈内连。

思路:把每条线都看做一个元素,这个元素可以有两个选择,圈外或者圈内,出现冲突的地方就是有两条线可能相交,那么这两条线一定是一条在外面一条在里面。所以就可以用2*i/2*i+1来表示在第i条线里面或者外面。这样就可以连边,如果i到j可能相交,那么i->j' , j'->i, i'->j,j->i'。这里每次加4条边,所以记得边的数量一定要够大。

然后就是缩点和判断i和i‘是否在同一个联通块内了。

这题的数据是真心挺弱的,我判断的时候写错了只到m,都过了....


#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int maxn = 1e3 + 50, maxm = 5e2 + 50, maxe = 1e6;int d[maxm][2];int n,m,a,b;struct node{    int to,next;    node(){}    node(int a,int b){to = a; next = b;}}edge[maxe];int h[maxn], low[maxn] , dfn[maxn], inS[maxn], S[maxe], fid[maxn];int edgenum , tot, snum;bool check(int a,int b){    int judge = 0;    for(int i = d[a][0] + 1; i < d[a][1]; i++)        if(i == d[b][0] || i == d[b][1])         judge ++;    if(judge == 2 || judge == 0) return false;    else return true;}void add(int f,int t){    edge[edgenum] = node(t,h[f]);    h[f] = edgenum++;}void pre(){    edgenum = tot = snum = 0;    for(int i = 0; i <= n ; i++)        h[i] = -1,inS[i] = 0;    for(int i = 0; i < m ; i++)    for(int j = i+1; j < m ; j++)    {        if(check(i,j))        {            add(2*i,2*j+1);  add(2*j+1,2*i);            add(2*i+1,2*j);  add(2*j,2*i+1);        }    }}void tarjan(int u){    low[u] = dfn[u] = ++tot;    S[++snum] = u;  inS[u] = 1;    for(int i = h[u]; ~i; i = edge[i].next)    {        int v = edge[i].to;        if(!dfn[v])        {            tarjan(v);            low[u] = min(low[u],low[v]);        }        else if(inS[v])            low[u] = min(low[u],dfn[v]);    }    if(low[u] == dfn[u])    {        while(1)        {            int v = S[snum--];            inS[v] = 0;            fid[v] = u;            if(v == u) break;        }    }}void solve(){    for(int i = 0; i < 2*m; i++)        if(!dfn[i]) tarjan(i);    int flag = 0;    for(int i = 0; i < 2*m; i++)        if(fid[i] == fid[i^1]) flag = 1;    flag == 0? printf("panda is telling the truth...\n"):printf("the evil panda is lying again");}int main(){    scanf("%d%d",&n,&m);    for(int i = 0; i < m; i++)    {        scanf("%d%d",&a,&b);        d[i][0] = a, d[i][1] = b;        if(a > b) swap(d[i][0],d[i][1]);    }    pre();    solve();    return 0;}


POJ 3683

题意:

有个牧师要参加n场婚礼,他可以选择参加n场婚礼的前一部分或者后一部分,肯定会有冲突的时候,现在问你牧师可以参加每一场婚礼吗。

思路:

2-sat问题是需要抽象一下,这题很明显,把一个婚礼看作一个元素,肯定只能选这个元素的前半部分或者是后半部分,这里元素间的冲突就是发生时间冲突。这里的两个元素间的四个时间段都要判断一下,然后把他们当作a and b = 0,也就是不能同时取。加边a->b', b - > a';

注意8:30结束 8:30可以开始的...还有就是顺序问题,你的输出必须是和输入一致的。

讲真,这个代码长度长的我有点害怕,然后我搜了一下别人的题解,发现别人没用反向建图+topsort + 染色?!(!?),然后我就又打了一份代码交了上去发现还真ac了。据解释是因为在求强连通分量的时候就已经把拓扑逆序求了出来,然后后面的原理我并不是很清楚了,有兴趣可自行百度~

代码:

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <queue>using namespace std;const int maxn = 2e3 + 10,maxe = 1e6;int n;struct coup{int s,t;} d[maxn];struct node{    int to,next;    node(){}    node(int a,int b){to = a; next = b;}}edge[maxe << 2],nedge[maxe << 2];int edgenum , tot , snum , num;int h[maxn], low[maxn], dfn[maxn], inS[maxn], S[maxn];int fid[maxn],h1[maxn], deg[maxn], isf[maxn], ans[maxn], col[maxn] ,out[maxn][4];vector<int> f[maxn];bool check(int as,int ap,int bs,int bp)      //检查是否有时间冲突{  return (ap <= bs || as >= bp); }void add(int f,int t){    edge[edgenum] = node(t,h[f]);    h[f] = edgenum ++;}void adde(int f,int t){    nedge[num] = node(t,h1[f]);    h1[f] = num++;}void pre(){    edgenum = tot = snum = num = 0;    for(int i = 0; i < 2*n; i++)        h[i] = fid[i] = h1[i] = -1,        low[i] = dfn[i] = isf[i] = ans[i] = col[i] = deg[i] = 0,        f[i].clear();    for(int i = 0; i <  2*n ; i++)    for(int j = i+1; j < 2*n ; j++)    {        if(i == (j^1)) continue;        int as = d[i].s , ap = d[i].t;        int bs = d[j].s , bp = d[j].t;        if(check(as, ap , bs , bp )) continue;        add(i,j^1);  add(j,i^1);    }}void tarjan(int u){    low[u] = dfn[u] = ++tot;    inS[u] = 1; S[++snum] = u;    for(int i = h[u]; ~i; i = edge[i].next)    {        int v = edge[i].to;        if(!dfn[v])        {            tarjan(v);            low[u] = min(low[u],low[v]);        }        else if(inS[v])            low[u] = min(low[u], dfn[v]);    }    if(low[u] == dfn[u])    //强连通分量    {        isf[u] = 1;        while(1)        {            int v = S[snum--];            fid[v] = u; inS[v] = 0; f[u].push_back(v);            if(u == v) break;        }    }}int anum = 0;void topsort(){    anum = 0;    queue<int> q;    for(int i = 0; i < 2*n; i++)        if(isf[i] && deg[i] == 0) q.push(i);    while(!q.empty())    {        int u = q.front(); q.pop();        ans[anum++] = u;        for(int i = h1[u]; ~i ; i = nedge[i].next)        {            int v = nedge[i].to;            deg[v] -- ;            if(deg[v] == 0) q.push(v);        }    }}void dfs(int u){    col[u] = 2;    for(int i = h1[u]; ~i; i = nedge[i].next)    {        int v = nedge[i].to;        dfs(v);    }}void solve(){    for(int i = 0; i < 2 * n; i++)        if(!dfn[i]) tarjan(i);    int flag = 0;    for(int i = 0; i < 2 * n ; i ++)        if(fid[i] == fid[i^1])    {flag = 1;break;}    if(flag) {printf("NO\n"); return ;}    printf("YES\n");    for(int u = 0; u < 2*n; u++)    for(int i = h[u]; ~i; i = edge[i].next)    {        int v = edge[i].to;        if(fid[v] == fid[u]) continue;        adde(fid[v], fid[u]);       //反向建图        deg[fid[u]] ++;    }    topsort();    for(int i = 0; i < anum; i++)    {        int u = ans[i];        if(!col[u])        {            col[u] = 1;            for(int j = 0; j < f[u].size(); j++)            {                int did = fid[f[u][j]^1];                dfs(did);            }        }    }    for(int i = 0; i < anum; i++)    {        int u = ans[i];        if(col[u] == 2) continue;        for(int j = 0; j < f[u].size(); j++)        {            int v = f[u][j]/2;            int ta = d[f[u][j]].s, tb = d[f[u][j]].t;            int shh = ta/60, smm = ta % 60;            int thh = tb/60, tmm = tb % 60;            out[v][0] = shh,out[v][1] = smm, out[v][2] = thh, out[v][3] = tmm;        }    }    for(int i = 0; i < n; i++)        printf("%02d:%02d %02d:%02d\n",out[i][0],out[i][1],out[i][2],out[i][3]);}int main(){    while(~scanf("%d",&n))    {        int a,b,c,p,e;        for(int i = 0; i < n ; i++)        {            scanf("%d:%d%d:%d%d",&a,&b,&c,&p,&e);            d[2*i].s = a*60 + b; d[2*i].t = a*60 + b + e;            d[2*i+1].s = c*60 + p - e, d[2*i+1].t = c*60 + p;        }        pre();        solve();    }    return 0;}

无逆向建图+拓扑排序+ 染色版:

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <queue>using namespace std;const int maxn = 2e3 + 10,maxe = 1e6;int n;struct coup{int s,t;} d[maxn];struct node{    int to,next;    node(){}    node(int a,int b){to = a; next = b;}}edge[maxe << 2],nedge[maxe << 2];int edgenum , tot , snum , num , sccnum;int h[maxn], low[maxn], dfn[maxn], inS[maxn], S[maxn], scc[maxn];int ans[maxn];bool check(int as,int ap,int bs,int bp)      //检查是否有时间冲突{  return (ap <= bs || as >= bp); }void add(int f,int t){    edge[edgenum] = node(t,h[f]);    h[f] = edgenum ++;}void pre(){    edgenum = tot = snum = num = sccnum = 0;    for(int i = 0; i < 2*n; i++)        h[i] = -1,low[i] = dfn[i] = 0;    for(int i = 0; i <  2*n ; i++)    for(int j = i+1; j < 2*n ; j++)    {        if(i == (j^1)) continue;        int as = d[i].s , ap = d[i].t;        int bs = d[j].s , bp = d[j].t;        if(check(as, ap , bs , bp )) continue;        add(i,j^1);  add(j,i^1);    }}void tarjan(int u){    low[u] = dfn[u] = ++tot;    inS[u] = 1; S[++snum] = u;    for(int i = h[u]; ~i; i = edge[i].next)    {        int v = edge[i].to;        if(!dfn[v])        {            tarjan(v);            low[u] = min(low[u],low[v]);        }        else if(inS[v])            low[u] = min(low[u], dfn[v]);    }    if(low[u] == dfn[u])    //强连通分量    {        sccnum++;        while(1)        {            int v = S[snum--];            scc[v] = sccnum; inS[v] = 0;            if(u == v) break;        }    }}int anum = 0;void solve(){    for(int i = 0; i < 2 * n; i++)        if(!dfn[i]) tarjan(i);    int flag = 0;    for(int i = 0; i < 2 * n ; i ++)        if(scc[i] == scc[i^1])    {flag = 1;break;}    if(flag) {printf("NO\n"); return ;}    printf("YES\n");    for(int i = 0; i < 2*n; i += 2)    {        int ta,tb;        if(scc[i] < scc[i+1])            ta = d[i].s, tb = d[i].t;        else ta = d[i+1].s, tb = d[i+1].t;        printf("%02d:%02d %02d:%02d\n",ta/60,ta%60,tb/60,tb%60);    }}int main(){    //freopen("D://in.txt","r",stdin);    while(~scanf("%d",&n))    {        int a,b,c,p,e;        for(int i = 0; i < n ; i++)        {            scanf("%d:%d%d:%d%d",&a,&b,&c,&p,&e);            d[2*i].s = a*60 + b; d[2*i].t = a*60 + b + e;            d[2*i+1].s = c*60 + p - e, d[2*i+1].t = c*60 + p;        }        pre();        solve();    }    return 0;}

POJ 3678

题意:有对xi和yi有几种操作OR AND XOR并知道他们得出的值,问最终这n个元素可以满足给出的所有式子吗

思路: 如果要是仔细的看了上面的那篇讲解博客,会发现这道题基本包括了所有模型...a,b不能同时选,只能选a和ab必须同时选。那么就是加边的问题啦,这里比较特殊的就是必须选a,我还没理解为何连边a'->a,如果之后完全明白了估计会过来补。

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int maxn = 2e3 + 10, maxe = 1e6 + 50;int n,m;struct node{    int to,next;    node(){}    node(int a,int b){to = a; next = b;}}edge[maxe << 2];int h[maxn], low[maxn] , dfn[maxn] , scc[maxn] , S[maxn], inS[maxn];int edgenum , tot , snum;void init(){    edgenum = tot = snum = 0;    for(int i = 0; i < 2*n; i++)        h[i] = -1,low[i] = dfn[i] = 0;}void add(int f,int t){    edge[edgenum] = node(t,h[f]);    h[f] = edgenum ++;}void tarjan(int u){    low[u] = dfn[u] = ++tot;    S[++snum] = u; inS[u] = 1;    for(int i = h[u]; ~i; i = edge[i].next)    {        int v = edge[i].to;        if(!dfn[v])        {            tarjan(v);            low[u] = min(low[u],low[v]);        }        else if(inS[v])            low[u] = min(low[u],dfn[v]);    }    if(low[u] == dfn[u])    {        int v = -1;        while(v != u)        {            v = S[snum--];            scc[v] = u; inS[v] = 0;        }    }}void solve(){    for(int i = 0; i < 2*n; i++)        if(!dfn[i]) tarjan(i);    int flag = 0;    for(int i = 0; i < 2*n; i += 2)        if(scc[i] == scc[i+1]) {flag = 1; break;}    flag == 0 ? printf("YES\n") : printf("NO\n");}int main(){    char s[10];    int a,b,c;    scanf("%d%d",&n,&m);    init();    for(int i = 0; i < m ; i++)    {        scanf("%d%d%d%s",&a,&b,&c,s);        if(s[0] == 'A')        {            if(c == 1)                add(2*a,2*a+1),add(2*b,2*b+1),                add(2*a+1,2*b+1),add(2*b+1,2*a+1);            else                add(2*a+1,2*b), add(2*b+1,2*a);        }        else if(s[0] == 'O')        {            if(c == 0)                add(2*a+1,2*a), add(2*b+1,2*b),                add(2*a,2*b), add(2*b,2*a);            else                add(2 * a,2 * b + 1), add(2 * b,2 * a + 1);        }        else        {            if(c == 0)                add(2 * a,2 * b), add(2 * b,2 * a),                add(2*a+1,2*b+1), add(2*b+1,2*a+1);            else                add(2*a,2*b+1), add(2*b,2*a+1),                add(2*a+1,2*b), add(2*b+1,2*a);        }    }    solve();    return 0;}

poj3648

题意: 题意有点....乱.......

思路: 因为只有新娘看得见对面的, 所以考虑在新娘这边的人,如果跟新娘新郎无关的,有a->b有关系,那么,这两个满足的是不能同时为坐在新娘对面,而我们选的是坐在新娘这边的人,所以连边a'->b,b'->a;

相信都看到了一句话..与新娘新郎无关 的情况:),没错,这道题的数据就是把新郎新娘有关系的也输入了进去,对新郎的情况要特殊处理,这里必须得选,因为新娘不能看到他/她,而新娘有关系的情况就不用处理了,:) 这道题我没看discuss就把这种情况蒙了出来,感觉自己的这个AC仿佛正在看着我....

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int maxn = 100, maxe = 1e4 + 10;int n,m;struct node{    int to,next;    node(){}    node(int a,int b){to = a; next = b;}}edge[maxe << 1];int edgenum,tot,snum,scnum;int low[maxn], dfn[maxn], h[maxn], inS[maxn], S[maxe];int scc[maxn];void init(){    for(int i = 0; i < 2*n; i++)        low[i] = dfn[i] = scc[i] = 0, h[i] = -1;    edgenum = tot = snum = scnum = 0;}void add(int f,int t){    edge[edgenum] = node(t,h[f]);    h[f] = edgenum++;}void tarjan(int u){    low[u] = dfn[u] = ++tot;    S[++snum] = u; inS[u] = 1;    for(int i = h[u]; ~i ; i = edge[i].next)    {        int v = edge[i].to;        if(!dfn[v])        {            tarjan(v);            low[u] = min(low[u],low[v]);        }        else if(inS[v])            low[u] = min(low[u],dfn[v]);    }    if(low[u] == dfn[u])    {        ++scnum;    int v = -1;        while(v != u)        {            v = S[snum--];            scc[v] = scnum; inS[v] = 0;        }    }}void solve(){    for(int i = 0; i < 2*n ; i++)        if(!dfn[i]) tarjan(i);    int flag = 0;    //cout << "scnum " << scnum << endl;    for(int i = 0; i < 2*n; i += 2)        if(scc[i] == scc[i+1]) {flag = 1; break;}    if(flag){printf("bad luck\n"); return;}    for(int i = 2; i < 2*n; i += 2)    {        if(i/2 != 1) printf(" ");        if(scc[i] < scc[i+1])            printf("%dw",i/2);        else printf("%dh",i/2);    }    printf("\n");}int main(){    char a[10],b[10];    //freopen("D:\\in.txt","r",stdin);    while(~scanf("%d%d",&n,&m) &&  n+m)    {        init();        for(int i = 0; i < m ; i++)        {            scanf("%s%s",a,b);            int alen = strlen(a), blen = strlen(b);            char c = a[alen - 1] , d = b[blen - 1];            int an = 0 , bn = 0,ida = 0,idb = 0;            an = alen == 3 ? (a[0] - '0')*10 + a[1] - '0' : a[0] - '0';            bn = blen == 3 ? (b[0] - '0')*10 + b[1] - '0' : b[0] - '0';            ida = c == 'h' ? 2*an + 1 : 2*an;            idb = d == 'h' ? 2*bn + 1 : 2*bn;            if(ida > idb) swap(ida,idb);            if(ida/2 == 0 && ida % 2 == 0) continue;            if(ida/2 == 0 && ida % 2 == 1)                add(idb^1,idb);            add(ida^1,idb);            add(idb^1,ida);        }        solve();    }    return 0;}




原创粉丝点击