2-sat学习小结

To be continued…

你问我2-sat的定义???
一口血喷出来
这里写图片描述
这里写图片描述
总之我们就开始做吧
其实我们只需要把能推出的关系连边
比如a存在时b一定存在,则连边a->b
a存在时b一定不存在,则连边a->┐b
然后强连通分量缩点,拓扑排序
这个时候呢我们就要安利一波Korasaju算法了
http://blog.csdn.net/qq_35541672/article/details/79318738
因为我们可以发现,这样求出的强连通分量中,我们产生的顺序倒过来就必然是拓扑序了

例题1 poj 3678

我也不知道第一道例题放什么好,干脆就直接放大版题好了
故事背景?不存在的,这是道裸题
告诉你类似a or/xor/and b=0/1的关系,让你求能否有满足这一条件的取值

题解

那我们就来退吧
设a表示取1,a’表示取0
和为1 a<->b
和为0 a->b’,b->a’
或为1 a’->b,b’->a
或为0 a’<->b’
异或为1 a<->b’ a’<->b
异或为0 a<->b a’<->b’
就这样构图,然后跑Korasaju,检查一下a和a’是否在同一个强连通分量中
代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
int head[2005],cnt;
int n,m;
struct node{
    int u,v,next,rev;
    bool ban;
}edge[10000005];
char op[4];
void add_edge(int u,int v,int rev,bool ban){
    cnt++;
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].rev=rev;
    edge[cnt].ban=ban;
    edge[cnt].next=head[u];
    head[u]=cnt;
}
void add(int u,int v){
    add_edge(u,v,cnt+2,0);
    add_edge(v,u,cnt,1);
}
bool vis[2005];
int scc[2005],sccc;
stack<int> S;
void dfs1(int u){
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(edge[i].ban||vis[v])
            continue ;
        dfs1(v);
    }
    S.push(u);
}
void dfs2(int u,int num){
    scc[u]=num;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(edge[i].ban||scc[v])
            continue;
        dfs2(v,num);
    }
}
void find_scc(){
    for(int i=0;i<2*n;i++){
        if(!vis[i])
            dfs1(i);
    }
    for(int i=1;i<=cnt;i+=2){
        edge[i].ban=1;
        edge[i+1].ban=0;
    }
    while(!S.empty()){
        int x=S.top();
        S.pop();
        if(scc[x])
            continue ;
        sccc++;
        dfs2(x,sccc);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b,c;
        scanf("%d%d%d %s",&a,&b,&c,op);
        if(op[0]=='A'){
            if(c==1){
                add(a+n,a);
                add(b+n,b);
                add(a,b);
                add(b,a);
            }
            else{
                add(a,b+n);
                add(b,a+n);
            }
        }
        if(op[0]=='O'){
            if(c==1){
                add(a+n,b);
                add(b+n,a);
            }
            else{
                add(a,a+n);
                add(b,b+n);
                add(a+n,b+n);
                add(b+n,a+n);
            }
        }
        if(op[0]=='X'){
            if(c==1){
                add(a,b+n);
                add(b+n,a);
                add(b,a+n);
                add(a+n,b);
            }
            else{
                add(a,b);
                add(b,a);
                add(a+n,b+n);
                add(b+n,a+n);
            }
        }
    }
    find_scc();
    for(int i=0;i<n;i++)
        if(scc[i]==scc[i+n]){
            printf("NO\n");
            return 0;
        }
    printf("YES\n");
}

例题2 POJ 2723 Get Luffy Out

有2n把钥匙(编号从0开始),分成了n对,每队你只能选择一个,在你选择钥匙后,你会去开门,每个门有两个数字,你只要有跟其中一个数字相同的钥匙就能打开它(有时这两个数字是相同的),你只有打开前面的门后才能打开后面的。请问最多开多少门

题解

N对钥匙我们可以看成是N对点,门的关系可以看成or=1
那么问题来了,开多少门应该怎么计算呢?
显然是满足单调性的,所以可以二分
然而当时我是倒着计算...好像数据水,卡在1954ms过去了
代码(之前1954ms的代码就不拿出来献丑了)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int N=2105,M=20005;
struct node{
    int v,nxt;
    bool ban;
}edge[M];
int head[N],mcnt;
void add_edge(int u,int v,bool ban){
    mcnt++;
    edge[mcnt].v=v;
    edge[mcnt].nxt=head[u];
    edge[mcnt].ban=ban;
    head[u]=mcnt;
}
void add(int u,int v){
    add_edge(u,v,0);
    add_edge(v,u,1);
}
stack<int>s;
bool vis[N];
void dfs1(int u){
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        if(edge[i].ban||vis[v])
            continue ;
        dfs1(v);
    }
    s.push(u);
}
int scc[N],scccnt;
void dfs2(int u){
    scc[u]=scccnt;
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        if(edge[i].ban||vis[v])
            continue ;
        dfs2(v);
    }
}
int n,m;
void find_scc(){
    scccnt=0;
    memset(vis,0,sizeof vis);
    for(int i=0;i<2*n;i++){
        if(!vis[i])
            dfs1(i);
    }
    for(int i=1;i<=mcnt;i+=2){
        edge[i].ban=1;
        edge[i+1].ban=0;
    }
    memset(vis,0,sizeof vis);
    while(!s.empty()){
        int u=s.top();
        s.pop();
        if(!vis[u]){
            scccnt++;
            dfs2(u);
        }
    }
}
int opp[N];
int f[M][2];
bool check(int x){
    memset(head,0,sizeof head);
    mcnt=0;
    for(int i=1;i<=x;i++){
        add(opp[f[i][0]],f[i][1]);
        add(opp[f[i][1]],f[i][0]);
    }
    find_scc();
    for(int i=0;i<n;i++)
        if(scc[i]==scc[opp[i]])
            return false;
    return true;
}
int main()
{
    while(scanf("%d%d",&n,&m)==2){
        if(n==0&&m==0)
            return 0;
        for(int i=1;i<=n;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            opp[a]=b,opp[b]=a;
        }
        for(int i=1;i<=m;i++){
            scanf("%d%d",&f[i][0],&f[i][1]);
        }
        int l=0,r=m+1;
        while(l+1<r){
            int mid=(l+r)>>1;
            if(check(mid))
                l=mid;
            else
                r=mid;
        }
        printf("%d\n",l);
    }
}

例题3 POJ 2749 Building roads

万恶的 Farmer John 有N个牛棚 给出它们的坐标
现在给出两个中转站的坐标,每个牛棚只能和一个中转站相连
有的牛棚只能连到相同中转站,有的只能连到不同中转站
请问任两个牛棚之间的最大距离最小是多少
(连向同一个中转站的距离(设均连向s1)为a->s1+s1->b,
不同(设a连s1,b连s2)则为a->s1+s1->s2+s2->b)

题解

将每个牛棚连向哪边作为对立的点。
要求相同则xor=0,要求不同则xor=1
二分这个最大距离
预处理一下,如果经过s1的距离>d,连边b->a’,a->b’,其他的同理

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
int head[10005],cnt,bhead[10005];
int head1[10005],cnt1,bhead1[10005];
struct node{
    int u,v,next;
}edge[9000005],edge1[9000005];
inline int Abs(int x){
    if(x>0)
        return x;
    return -x;
}
inline int Max(int x,int y){
    return x>y?x:y;
}
inline int dist(int x0,int y0,int x1,int y1){
    return Abs(x0-x1)+Abs(y0-y1);
}
void add_edge(int u,int v){
    cnt++;
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
}
void add_edge1(int u,int v){
    cnt1++;
    edge1[cnt1].u=u;
    edge1[cnt1].v=v;
    edge1[cnt1].next=head1[u];
    head1[u]=cnt1;
}
void add1(int u,int v){
    add_edge(u,v);
    add_edge1(v,u);
}
void add2(int u,int v){
    add_edge(u,v);
    add_edge(v,u);
    add_edge1(u,v);
    add_edge1(v,u);
}
bool vis[10005];
int scc[10005],sccc;
pair<int,int> p[10005];
int n,a,b,x1,y1,x2,y2,ddd;
stack<int> S;
void dfs1(int u){
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(vis[v])
            continue ;
        dfs1(v);
    }
    S.push(u);
}
void dfs2(int u,int num){
    scc[u]=num;
    for(int i=head1[u];i;i=edge1[i].next){
        int v=edge1[i].v;
        if(scc[v])
            continue;
        dfs2(v,num);
    }
}
void find_scc(){
    memset(vis,0,sizeof vis);
    memset(scc,0,sizeof scc);
    sccc=0;
    for(int i=1;i<=2*n;i++){
        if(!vis[i])
            dfs1(i);
    }
    while(!S.empty()){
        int x=S.top();
        S.pop();
        if(scc[x])
            continue ;
        sccc++;
        dfs2(x,sccc);
    }
}
bool check(int cap){
    int cntt=cnt;
    memcpy(bhead,head,sizeof head);
    memcpy(bhead1,head1,sizeof head1);
    for(int i=1;i<=2*n;i++){
        int d1,d11,d2,d22;
            d1=d2=0;
        if(i<=n)
            d1=dist(x1,y1,p[i].first,p[i].second);
        else
            d2=dist(x2,y2,p[i-n].first,p[i-n].second);
        if(d1>cap){
            add1(i,i+n);
            continue ;
        }
        if(d2>cap){
            add1(i,i-n);
            continue ;
        }
        for(int j=i+1;j<=2*n;j++){
            d11=d22=0;
            if(j<=n)
                d11=dist(x1,y1,p[j].first,p[j].second);
            else
                d22=dist(x2,y2,p[j-n].first,p[j-n].second);
            if(d11>cap){
                add1(j,j+n);
                continue ;
            }
            if(d22>cap){
                add1(j,j-n);
                continue ;
            }
            if(i<=n&&j<=n&&d1+d11>cap){
                add1(i,j+n);
                add1(j,i+n);
            }
            if(i<=n&&j>n&&d1+ddd+d22>cap){
                add1(i,j-n);
                add1(j,i+n);
            }
            if(i>n&&j>n&&d2+d22>cap){
                add1(i,j-n);
                add1(j,i-n);
            }
            if(i>n&&j<=n&&d2+ddd+d11>cap){
                add1(i,j+n);
                add1(j,i-n);
            }
        }
    }
    find_scc();
    cnt=cntt;
    cnt1=cntt;
    memcpy(head,bhead,sizeof head);
    memcpy(head1,bhead1,sizeof head1);
    for(int i=1;i<=n;i++)
        if(scc[i]==scc[i+n])
            return false;
    return true;
}
int main()
{
    scanf("%d%d%d%d%d%d%d",&n,&a,&b,&x1,&y1,&x2,&y2);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&p[i].first,&p[i].second);
    for(int i=1;i<=a;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add2(x,y+n);
        add2(y,x+n);
    }
    for(int i=1;i<=b;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add2(x,y);
        add2(x+n,y+n);
    }
    ddd=dist(x1,y1,x2,y2);
    if(!check(99999999)){
        printf("-1\n");
        return 0;
    }
    int l=0,r=10000000;
    while(l<r){
        int mid=(l+r)/2;
        if(check(mid)){
            r=mid;
        }
        else
            l=mid+1;
    }
    printf("%d",r);
}

杂谈

另:在关系只有xor的时候,我们可以用并查集来代替求强连通分量。


关于输出方案的问题
如前所说,使用Korasaju算法的话,scc编号倒过来就是拓扑序,然后我们就能快速出解了

find_scc();
for(int i=1;i<=n;i++)
    if(scc[i]==scc[i+n]){
        printf("no solution\n");
        return 0;
    }
for(int i=1;i<=n;i++)
    if(scc[i]>scc[i+n])
        printf("%d ",i);
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值