zoukankan      html  css  js  c++  java
  • poj-3648(2-sat)

    题目链接:http://poj.org/problem?id=3648

    题意:有n对夫妻坐成两排,其中第0对为新娘新郎。有m对奸情,新娘不愿意看到对面排存在奸情,问是否有合理方案,有输出一组解,否则输出"bad luck";

    思路:

    每对夫妻要么和新郎一排,要么和新娘一排所以容易想到用2-sat.

    我们将丈夫编号为1~n,其对应的妻子编号+n(下面用i表示丈夫,i'表示妻子).

    每对奸情则即是矛盾,则与其不矛盾的点连边,比如a与b矛盾,则连a->b',b->a',其意思是选a 必须连b',不能连b。

    这里用一个数组(kx[])表示该点是否可选,或没被选。 初始值为0,表示未被选,1表示可选及已选,-1表示不可选。

    每次选择了一个点后,就暴力搜接下来必须选择的点,然后把他们都选择(相连边的点遵循要选一起选的原则),其伴侣都标记为不选择。如果不合法就换一种方式搜。

    连边时要加一条新娘到新郎的边,因为新郎也可能有奸情,使其矛盾。

    过程例如以下:a与新郎有奸情,矛盾,在开始建图时会使a->新娘,当如果爆搜选了a,则下面选新娘,标记新郎不可选,再通过建的这条边,发现爆搜a不可行,这样就避免了新郎与奸情坐一侧,这点很重要,而且我想了很久很久。。。。

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define inf 0x3f3f3f3f
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+10;
    struct node{
        int u,v,next;
    }e[maxn];
    int head[maxn],kx[maxn],n,m,top,cnt,st[maxn];
    //kx数组判断是否可选,初始值为0, 1为可选且已选,-1为不可选。 
    int cp(int x){ return x<=n?x+n:x-n; }//返回cp编号 
    void add(int u,int v){e[cnt].u=u,e[cnt].v=v,e[cnt].next=head[u],head[u]=cnt++;}
    
    bool dfs(int x)//爆搜,判断编号x是否可选 
    {
        if(kx[x]==1)    return true;//可选,且已被选。 
        if(kx[x]==-1)    return false;//不可选 
        kx[x]=1,kx[cp(x)]=-1,st[++top]=x;//没被选,但是可被选,所以选入,其cp标为不可选。 
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            if(!dfs(e[i].v))//判断相连的点是否可选,要选一起选。 
                return false;
        }
        return true;
    }
    
    void solve()
    {
        for(int i=1;i<=n;i++)
        {
            if(kx[i])//由0~i-1 连边判断已被选,不用进行下面操作    
                continue;
            top=0;
            if(!dfs(i))//kx[i]==0,没被选 ,且不可选 
            {
                while(top)//返回由i推到已选点,重新变成没选状态。 
                {
                    kx[st[top]]=kx[cp(st[top])]=0;
                    top--;
                }
                if(!dfs(i+n))//i不可选,且i+n也不可选,说明寻找不到答案。 
                {
                    cout<<"bad luck"<<endl;
                    return ;    
                }    
            }
        }    
        for(int i=1;i<n;i++)//可以找到解 
        {
            if(kx[i]==-1)//当i不可选,则表示新娘不可见,放到新娘一边 
                cout<<i<<"h ";
            else//否则i+n不可选,所以i的妻子放在新娘一边 
                cout<<i<<"w ";
        }
        cout<<endl;
    }
    
    int main()
    {
        std::ios::sync_with_stdio(false);
        int x,y;
        char x1,y1;
        while(cin>>n>>m)
        {
            if(!n&&!m)    break;    
            memset(kx,0,sizeof(kx));
            memset(head,-1,sizeof(head));
            cnt=0;
            for(int i=0;i<m;i++)
            {
                cin>>x>>x1>>y>>y1;
                if(!x) x=n;
                if(!y) y=n;
                if(x1=='h'&&y1=='h')        add(x,y+n),add(y,x+n);
                else if(x1=='h'&&y1=='w')    add(x,y),add(y+n,x+n);
                else if(x1=='w'&&y1=='h')    add(x+n,y+n),add(y,x);
                else if(x1=='w'&&y1=='w')    add(x+n,y),add(y+n,x);
            }
            add(2*n,n);//加一条新娘到新郎的边,因为新郎可能有奸情 
            solve();
        }    
        return 0;
    }
    View Code

    上面采用爆搜的方法,时间复杂度为O(n*m),有点高,但如果求字典序最小方案只能爆搜。

    而如果采用拓扑排序时间复杂度先对较低,为O(m),但只能求出一组随机解,这题可以。

    怎么个思路呢?

    在连通分支中的点,如果选了一个点,那么这个连通分支的点都要选,证明就不证了。

    我们将先连的连通分支反向连接。再拓扑排序继续用kx[]判断是否可选即可。

    为什么反向连边呢?

    "选择”标记是没有进行传递的,也就是说正向边是没有利用到的,传递的都是“不选择”标记,也就是说走的都是反边

    具体可以看大佬的博客:原来2-SAT是这么一回事

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #define inf 0x3f3f3f3f
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+10;
    struct node{
        int u,v,next;
    }e[maxn],e2[maxn];
    int h[maxn],h2[maxn];
    int st[maxn],visit[maxn],dfn[maxn],low[maxn],belong[maxn];
    int kx[maxn],opp[maxn],in[maxn],n,m,cnt,cnt2,num,top,tot;
    //opp数组存为对立点,相当于连通分量矛盾的点(不可同时选)。 
    void init()
    {
        memset(h,-1,sizeof(h));
        memset(h2,-1,sizeof(h2));
        memset(visit,0,sizeof(visit));
        memset(dfn,0,sizeof(dfn));
        memset(belong,0,sizeof(belong));
        memset(kx,0,sizeof(kx));
        memset(opp,0,sizeof(opp));
        memset(in,0,sizeof(in));
        cnt=cnt2=top=num=tot=0;
    }
    
    void add(int u,int v)
    {
        e[cnt].u=u;
        e[cnt].v=v;
        e[cnt].next=h[u];
        h[u]=cnt++;
    }
    
    void add2(int u,int v)
    {
        e2[cnt2].u=u;
        e2[cnt2].v=v;
        e2[cnt2].next=h2[u];
        h2[u]=cnt2++; 
    }
    
    void tarjan(int u)//tarjan缩点 
    {
        low[u]=dfn[u]=++tot;
        visit[u]=1;
        st[++top]=u;
        for(int i=h[u];i!=-1;i=e[i].next)
        {
            int v=e[i].v;
            if(!dfn[v])
            {
                tarjan(v);
                low[u]=min(low[u],low[v]); 
            }    
            if(visit[v])
                low[u]=min(low[u],dfn[v]); 
        }
        if(low[u]==dfn[u])
        {
            int t;
            num++;
            do{
                t=st[top--];
                visit[t]=0;
                belong[t]=num;
            }while(t!=u);
        }
    }
    
    void topsort()//拓扑排序 
    {
        top=0;
        for(int i=1;i<=num;i++)
        {
            if(!in[i])
                st[++top]=i;
        }
        while(top)
        {
            int u=st[top--];
            if(!kx[u])
            {
                kx[u]=1;
                kx[opp[u]]=-1;
            }
            for(int i=h2[u];i!=-1;i=e2[i].next)
            {
                int v=e2[i].v;
                in[v]--;
                if(!in[v])
                    st[++top]=v;
            }
        }
    }
    
    void solve()
    {
        for(int i=1;i<=2*n;i++)
        {
            if(!dfn[i])
                tarjan(i);
        }
        for(int i=1;i<=n;i++)
        {
            if(belong[i]==belong[i+n])//不成立,矛盾点在同一连通分量里 
            {
                cout<<"bad luck"<<endl;
                return ;
            }
            opp[belong[i]]=belong[i+n];
            opp[belong[i+n]]=belong[i];
        }
        for(int i=1;i<=2*n;i++)//建连通分量图 
        {
            for(int j=h[i];j!=-1;j=e[j].next)
            {
                int v=e[j].v;
                if(belong[v]!=belong[i])//两个点不同才相连 
                {
                    add2(belong[v],belong[i]);//反向连边 
                    in[belong[i]]++;    
                } 
            }
        }
        topsort();
        for(int i=1;i<n;i++)//解的方式和上面一致。 
        {
            if(kx[belong[i]]==-1)
                cout<<i<<"h ";
            else
                cout<<i<<"w ";
        }
        cout<<endl;
    }
    
    int main()
    {
        std::ios::sync_with_stdio(false);
        int x,y;
        char x1,y1;
        while(cin>>n>>m)
        {
            if(!n&&!m)    
                break;
            init();
            for(int i=0;i<m;i++)
            {
                cin>>x>>x1>>y>>y1;
                if(!x)    x=n;
                if(!y)    y=n;
                if(x1=='h'&&y1=='h')    add(x,y+n),add(y,x+n);
                if(x1=='h'&&y1=='w')    add(x,y),add(y+n,x+n);
                if(x1=='w'&&y1=='h')    add(x+n,y+n),add(y,x);
                if(x1=='w'&&y1=='w')    add(x+n,y),add(y+n,x);
            }
            add(2*n,n);
            solve();
        }
        return 0;
    }
    View Code
  • 相关阅读:
    luaPlus
    falagard cegui
    cegui 的透明纹理
    msvcprt.lib(MSVCP90.dll) : error LNK2005:已经在libcpmtd.lib(xmutex.obj) 中定义
    CEGUI
    SameText
    操作 Wave 文件(15): 合并与剪裁 wav 文件
    Delphi 的编码与解码(或叫加密与解密)函数
    操作 Wave 文件(13): waveOutGetVolume、waveOutSetVolume
    操作 Wave 文件(12): 使用 waveOut...重复播放 wav 文件
  • 原文地址:https://www.cnblogs.com/xiongtao/p/10611026.html
Copyright © 2011-2022 走看看