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

 

posted @ 2019-03-27 21:35  怀揣少年梦.#  阅读(318)  评论(0编辑  收藏  举报