hdu 3715 hdu 1816 hdu 4115 (2-sat)

三个2-sat问题,我总结一下自己的经验,我还是太菜,好久没做2-sat,又不太会建图了

总结:2-sat问题的核心就是建模

建模的思想很重要:

1.首先要怎么才能看出来是2-sat问题,对于每一个点,都有两种选择,则可以考虑是2-sat

比如让你在n组里面选n个,每组2个东西,这样就是2-sat的模型


2.确定是2-sat问题之后,就是要确定,拥有两种选择的对象,比如下面的例题里,有的是x[i]的取值有0或者1两种,有的是一串钥匙有两个,但你只能选择其中一个,则对于一个钥匙,它的取值有取或者不取,而不是对于这一串,取第一个还是第二个, 还有个问题是石头剪刀布的输赢,对面出石头,你不输的话要出石头或者布,这就是两种选择

这个拥有两种选择的对象有n个,然后你建图就是考虑i和i+n,最后tarjan后找i和i+n是否矛盾。


3.建图,建图是核心问题,但是一旦能够确定2-sat的对象,我感觉建图还是相对容易的,只要细心,不少考虑情况即可

要找到题目给的必定的条件,比如a和b不能同时存在,则a->~b,b->~a,如果a和b必须同时存在,则a->b,b->a,这种都要靠自己的感觉,没有什么确定的答案


总而言之,虽然我有好多题都不会做,有的是自己想出来了,有的没有想出来,但是感觉2-sat的方法还是有套路可循的,就是确定问题,寻找对象,寻找其中的关系


下面来看例题把:

简单的就不赘述了

hdu 3715

http://acm.hdu.edu.cn/showproblem.php?pid=3715

给你一个程序的伪代码,以及a,b,c,三个数组,还有个位置的x数组,x数组元素值为0或者1,问你如何设置x数组的值,使能通过的层数最大

首先确定是2-sat问题,其次要求层数最大,二分即可,对于每次二分的层数mid,把1-mid给的a,b,c数组用于建图

有两种选择的对象是x数组,假设x[i]=0为点i,x[i]=1为点i+mid

c=0时,x[a[i]]和x[b[i]]不能同时为0,即a[i]->b[i]+mid ,b[i]->a[i]+x;

c=1时,a[i]->b[i],b[i]->a[i],a[i]+x->b[i]+x,b[i]+x->a[i]+x;

c=2时,a[i]+x->b[i],b[i]+x->a[i];

如此建图,跑tarjan判可行性,即可

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX           405
#define   MAXN          1000005
#define   maxnode       10
#define   sigma_size    2
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   lrt           rt<<1
#define   rrt           rt<<1|1
#define   middle        int m=(r+l)>>1
#define   LL            long long
#define   ull           unsigned long long
#define   mem(x,v)      memset(x,v,sizeof(x))
#define   lowbit(x)     (x&-x)
#define   pii           pair<int,int>
#define   bits(a)       __builtin_popcount(a)
#define   mk            make_pair
#define   limit         10000

//const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const LL     INFF  = 0x3f3f;
const double pi    = acos(-1.0);
const double inf   = 1e18;
const double eps   = 1e-9;
const LL     mod   = 1e9+7;
const ull    mxx   = 1333331;

/*****************************************************/
inline void RI(int &x) {
      char c;
      while((c=getchar())<'0' || c>'9');
      x=c-'0';
      while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/

struct Edge{
    int u,v,next;
}edge[MAXN];
int dfn[MAX],low[MAX],belong[MAX],sstack[MAX];
int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数
int n,m;//n是点对个数             //Index是tarjan序
int instack[MAX];               //top是tarjan的栈元素个数
int a[10005],b[10005],c[10005];
void init(){
    mem(head,-1);
    mem(instack,0);
    mem(dfn,0);
    mem(belong,0);
    tot=0;
    top=0;
    cnt=0;
    Index=0;
}

void add_edge(int a,int b){
    edge[tot]=(Edge){a,b,head[a]};
    head[a]=tot++;
}

void tarjan(int u){//判断可行只需要一个tarjan即可
    dfn[u]=low[u]=++Index;
    sstack[++top]=u;
    instack[u]=1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        ++cnt;
        while(1){
            int k=sstack[top--];
            instack[k]=0;
            belong[k]=cnt;
            if(k==u) break;
        }
    }
}

bool check(int x){
    init();
    for(int i=0;i<x;i++){
        //if(a[i]==b[i]) continue;
        if(c[i]==0){
            add_edge(a[i],b[i]+n);
            add_edge(b[i],a[i]+n);
        }
        else if(c[i]==1){
            add_edge(a[i],b[i]);
            add_edge(b[i],a[i]);
            add_edge(a[i]+n,b[i]+n);
            add_edge(b[i]+n,a[i]+n);
        }
        else if(c[i]==2){
            add_edge(a[i]+n,b[i]);
            add_edge(b[i]+n,a[i]);
        }
    }
    for(int i=0;i<2*n;i++){
        if(!dfn[i]) tarjan(i);
    }
    for(int i=0;i<n;i++){
        if(belong[i]==belong[i+n]) return false;
    }
    return true;
}
int main(){
    //freopen("in.txt","r",stdin);
    int t;
    cin>>t;
    while(t--){
        cin>>n>>m;
        for(int i=0;i<m;i++) scanf("%d%d%d",&a[i],&b[i],&c[i]);
        int l=1,r=m;
        while(l<=r){
            int mid=(l+r)/2;
            //cout<<l<<" "<<r<<" "<<mid<<endl;
            if(check(mid)) l=mid+1;
            else r=mid-1;
        }
        cout<<r<<endl;
    }
    return 0;
}



hdu 1816

http://acm.hdu.edu.cn/showproblem.php?pid=1816

给你n串钥匙,每串上面有2个钥匙,一共2n个钥匙,都互不相同,一串上一个如果用了,另一个就不能使用,然后给你m层,每层上面有两个锁,对应两种钥匙(可能相同)

问你最多能跑多少层?

2-sat+二分,这题两种选择对象有点难找,早上做的不太认真,一直GG

首先同一串钥匙上的两个a,b,每个钥匙有取或者不取两种选择,所以我们找到了问题的对线

a->~b,b->~a,但是这只是一部分限制条件,因为你每次二分的时候,mid层上的门,也是有限制的,这个限制比较难发现,因为有两个锁,你必须打开其中之一的锁,比如两个锁分别是c,d,那么你至少得打开一个,所以并不是选了c就不能选d,而应该是选了~c就不能选~d,所以建图是~c->d,~d->c

然后跑tarjan即可(题意好像有点小问题,他说的是有m个门,但是最后个门不能同向下面,我理解成了只有m-1个门GG)

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX           4445
#define   MAXN          1000005
#define   maxnode       10
#define   sigma_size    2
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   lrt           rt<<1
#define   rrt           rt<<1|1
#define   middle        int m=(r+l)>>1
#define   LL            long long
#define   ull           unsigned long long
#define   mem(x,v)      memset(x,v,sizeof(x))
#define   lowbit(x)     (x&-x)
#define   pii           pair<int,int>
#define   bits(a)       __builtin_popcount(a)
#define   mk            make_pair
#define   limit         10000

//const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const LL     INFF  = 0x3f3f;
const double pi    = acos(-1.0);
const double inf   = 1e18;
const double eps   = 1e-9;
const LL     mod   = 1e9+7;
const ull    mxx   = 1333331;

/*****************************************************/
inline void RI(int &x) {
      char c;
      while((c=getchar())<'0' || c>'9');
      x=c-'0';
      while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/

struct Edge{
    int u,v,next;
}edge[MAX*MAX*2];
int dfn[MAX],low[MAX],belong[MAX],sstack[MAX];
int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数
            //Index是tarjan序
int instack[MAX];               //top是tarjan的栈元素个数
int aa[2500],bb[2500];
int c[2500],d[2500];
int pre[2500];
int n,m;
void init(){
    mem(head,-1);
    mem(instack,0);
    mem(dfn,0);
    mem(belong,0);
    tot=0;
    top=0;
    cnt=0;
    Index=0;
}

void add_edge(int a,int b){
    edge[tot]=(Edge){a,b,head[a]};
    head[a]=tot++;
}

void tarjan(int u){//判断可行只需要一个tarjan即可
    dfn[u]=low[u]=++Index;
    sstack[++top]=u;
    instack[u]=1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        ++cnt;
        while(1){
            int k=sstack[top--];
            instack[k]=0;
            belong[k]=cnt;
            if(k==u) break;
        }
    }
}

bool check(int x){
    init();
    for(int i=0;i<n;i++){
        add_edge(c[i],d[i]+2*n);
        add_edge(d[i],c[i]+2*n);
    }
    for(int i=1;i<=x;i++){
        add_edge(bb[i]+2*n,aa[i]);
        add_edge(aa[i]+2*n,bb[i]);
    }
    for(int i=0;i<4*n;i++){
        if(!dfn[i]) tarjan(i);
    }
    for(int i=0;i<2*n;i++){
        if(belong[i]==belong[i+2*n]) return false;
    }
    return true;
}
int main(){
    //freopen("in.txt","r",stdin);
    while(cin>>n>>m){
        if(n==0&&m==0) break;
        for(int i=0;i<n;i++) scanf("%d%d",&c[i],&d[i]);
        for(int i=1;i<=m;i++){
            scanf("%d%d",&aa[i],&bb[i]);
        }
        int l=1,r=m;
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid)) l=mid+1;
            else r=mid-1;
        }
        cout<<r<<endl;
    }
    return 0;
}




hdu 4115

http://acm.hdu.edu.cn/showproblem.php?pid=4115

两个人石头剪刀布,一共n轮,m个限制条件,已知第一个人n次出的是什么,问你在限制下,第二个人可能n轮都不输么,限制条件是某两次出的必须一样,或者必须不一样

做的时候还没总结到这么多,做完这题瞬间对2sat有了更多理解,首先这题如何看出来是2sat呢,对于n轮,你都不能输,所以你每轮都有两个出的方法,所以是两种选择,即为c,d,其次对象是每一轮,最后就是找每轮里的矛盾(两种都出了)。

建图嘛就简单了呗,根据条件,如果a,b轮必须相同,if c[a]!=c[b]...,if c[a]!=d[b],... if d[a]!=c[b]... if d[a]!=d[b] ...

如果必须不同 if c[a]==c[b]... if c[a]==d[b]... if d[a]==c[b]... if d[a]==d[b]...

考虑这样是否涵盖了所有的限制条件,我可以说是的,每一轮里的每个都和另一轮里的两个都讨论过了,所以是全面的考虑

通过这两题,我对2-sat建图的方法又有了更深刻的理解,原来只是理解了简单的建图,会套模版而已

改天再战

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX           20005
#define   MAXN          1000005
#define   maxnode       10
#define   sigma_size    2
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   lrt           rt<<1
#define   rrt           rt<<1|1
#define   middle        int m=(r+l)>>1
#define   LL            long long
#define   ull           unsigned long long
#define   mem(x,v)      memset(x,v,sizeof(x))
#define   lowbit(x)     (x&-x)
#define   pii           pair<int,int>
#define   bits(a)       __builtin_popcount(a)
#define   mk            make_pair
#define   limit         10000

//const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const LL     INFF  = 0x3f3f;
const double pi    = acos(-1.0);
const double inf   = 1e18;
const double eps   = 1e-9;
const LL     mod   = 1e9+7;
const ull    mxx   = 1333331;

/*****************************************************/
inline void RI(int &x) {
      char c;
      while((c=getchar())<'0' || c>'9');
      x=c-'0';
      while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/

struct Edge{
    int u,v,next;
}edge[MAX*8];
int dfn[MAX],low[MAX],belong[MAX],sstack[MAX];
int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数
            //Index是tarjan序
int instack[MAX];               //top是tarjan的栈元素个数
int c[10005],d[10005];
int n,m;
void init(){
    mem(head,-1);
    mem(instack,0);
    mem(dfn,0);
    mem(belong,0);
    tot=0;
    top=0;
    cnt=0;
    Index=0;
}

void add_edge(int a,int b){
    edge[tot]=(Edge){a,b,head[a]};
    head[a]=tot++;
}

void tarjan(int u){//判断可行只需要一个tarjan即可
    dfn[u]=low[u]=++Index;
    sstack[++top]=u;
    instack[u]=1;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        ++cnt;
        while(1){
            int k=sstack[top--];
            instack[k]=0;
            belong[k]=cnt;
            if(k==u) break;
        }
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    int t,kase=0;
    cin>>t;
    while(t--){
        int n,m;
        cin>>n>>m;
        init();
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            if(x==1){
                c[i]=1;
                d[i]=2;
            }
            else if(x==2){
                c[i]=2;
                d[i]=3;
            }
            else if(x==3){
                c[i]=3;
                d[i]=1;
            }
        }
        for(int i=0;i<m;i++){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            if(z==0){
                if(c[x]!=c[y]){
                    add_edge(x,y+n);
                    add_edge(y,x+n);
                }
                if(c[x]!=d[y]){
                    add_edge(x,y);
                    add_edge(y+n,x+n);
                }
                if(d[x]!=d[y]){
                    add_edge(x+n,y);
                    add_edge(y+n,x);
                }
                if(d[x]!=c[y]){
                    add_edge(x+n,y+n);
                    add_edge(y,x);
                }
            }
            else{
                if(c[x]==c[y]){
                    add_edge(x,y+n);
                    add_edge(y,x+n);
                }
                if(d[x]==d[y]){
                    add_edge(x+n,y);
                    add_edge(y+n,x);
                }
                if(c[x]==d[y]){
                    add_edge(x,y);
                    add_edge(y+n,x+n);
                }
                if(d[x]==c[y]){
                    add_edge(x+n,y+n);
                    add_edge(y,x);
                }
            }
        }
        for(int i=1;i<=2*n;i++){
            if(!dfn[i]) tarjan(i);
        }
        int flag=0;
        for(int i=1;i<=n;i++){
            if(belong[i]==belong[i+n]) flag=1;
        }
        kase++;
        printf("Case #%d: ",kase);
        if(flag) printf("no\n");
        else printf("yes\n");
    }
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值