现在的位置: 首页 > 综合 > 正文

poj 3207 poj 3678 谈2-sat建图

2014年02月03日 ⁄ 综合 ⁄ 共 4000字 ⁄ 字号 评论关闭

先回顾最裸的2-sat模型

某国有n个党派,每个党派在议会中恰有2个代表。现在要成立和平委员会 ,该会满足:每个党派在和平委员会中有且只有一个代表 如果某两个代表不和,则他们不能都属于委员会 代表的编号从1到2n,编号为2a-1、2a的代表属于第a个党派。

思想: 由于一些人不能同时被选入委员会,那么假设a和b不和,那么当选泽a的时候必选择b’,当选择b的时候必选择a’.那么我们可以根据这个关系建个图。这个图是对称的,由于假如有边a->b’,那么必有b->a’.然后我们在这个图中找到答案,但是假如这个图中有环并且某个党派的的两个状态(人)都在里边,那么说明无解,否则说明有解。

再简化这个模型:就是有n个点,每个点有取与不取两种状态,m组矛盾,每一组矛盾为i和j,则i与jj要么都选要么都不选,ii与j要么都选要么都不选,在i于jj建立有向边,在j与ii建立有向边,这里注意一点:建图的时候是建立有向边,并且是对称建立。这里还有一点,为什么是i-〉jj,j-〉ii而不是ii->jj,jj->i,后面在具体解释。

注意三点:1、两种状态;2、矛盾是什么;3、边的意义

先看简单一些的poj 3207

http://poj.org/problem?id=3207

题目大意是:圆环上n个点编号为0,1,2,3......n-1,连接m组点,连线要么在圆内要么在圆外,判断线是否有相交。

线有两种状态:圆内与圆外,需要判断线是不是相交,可能相交的情况就两种(见代码),这时候建立的是双向边,我是这么理解的:line1,line2同时在圆内会相交,同时在圆外也会相交,那么就意味着如果line1在圆内,那么line2必须在圆外,如果line2在圆外,则line1必在圆内,所以建立双向边,下一个例子poj 3678你会看到并不是所有情况都可以建立双向边。line1在圆内的状态与line2在圆外的状态相连,line1在圆外的状态与line2在圆内的状态相连,最后检查矛盾,某一个line会不会在同一个强联通分量里

贴代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
#define N 1005

int head[N],scnt,cnt,dfn[N],low[N],id[N],n,m,ee[N][2];

struct node{
    int to;
    int next;
}edge[500000];

stack<int>st;

void addedge(int u,int v,int k)
{
    edge[k].to=v;
    edge[k].next=head[u];
    head[u]=k;
}

void tarjan(int u)
{
    int v,k,min1;

    min1=dfn[u]=low[u]=cnt++;
    st.push(u);
    for(k=head[u];k!=-1;k=edge[k].next)
    {
        v=edge[k].to;
        if(dfn[v]==-1)tarjan(v);
        min1=min(min1,low[v]);
    }
    if(min1<low[u]){low[u]=min1;return;}
    do
    {
        id[v=st.top()]=scnt;
        st.pop();
        low[v]=n;
    }while(v!=u);
    scnt++;
}

void init()
{
    memset(dfn,-1,sizeof(dfn));
    memset(head,-1,sizeof(head));
    scnt=cnt=0;
}
int main()
{
    int i,j,num;

    scanf("%d%d",&n,&m);
    init();
    for(i=0;i<m;i++)
    {
        scanf("%d%d",&ee[i][0],&ee[i][1]);
        if(ee[i][0]>ee[i][1])swap(ee[i][0],ee[i][1]);

    }
    num=0;
    for(i=0;i<m;i++)
        for(j=i+1;j<m;j++)
        {
            if(ee[i][0]<ee[j][0]&&ee[j][0]<ee[i][1]&&ee[i][1]<ee[j][1]||ee[i][0]>ee[j][0]&&ee[i][0]<ee[j][1]&&ee[j][1]<ee[i][1])
            {
                addedge(i,j+m,num++);
                addedge(j+m,i,num++);
                addedge(j,i+m,num++);
                addedge(i+m,j,num++);
            }
        }
    for(i=0;i<m*2;i++)
        if(dfn[i]==-1)tarjan(i);
    bool flag=true;
    for(i=0;i<m;i++)
    {
        if(id[i]==id[i+m]){flag=false;break;}
    }
    if(flag)printf("panda is telling the truth...\n");
    else printf("the evil panda is lying again\n");

    return 0;
}

在看难一些的poj 3678

题目似乎难以看懂,主要是那个万恶的表格难以看懂...

表格按高中学得表格看吧,其实就跟计算机本来的&,|,^是一样的,看不懂也没关系:
建图:

先找两种状态:第i个点是  0和1

再找矛盾:比如i&j=0,建立边i=1->j=0,j=1->i=0,这时候千万别写成双向边,因为i&j=0,只能说明如果i为1,j必须为0,不能说明如果j为0,i 必须为1(i取0也可以),

注意不同。

比较难想的一点是,i&j=1,怎么建图,答案是建立边i=0->i=1,j=0->j=1

为什么?
看边的意义:i->j意味着如果i装态,则必有j状态,好了,i=0->i=1就意味着,i必定为1了

检查矛盾的方式还是那样,看矛盾的状态是不是在同一个强联通分量里,其实做了缩点处理

贴代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<cstring>
using namespace std;
#define N 2100
#define M 1000005

int head[N],dfn[N],low[N],id[N];
/*最后再来检查数组范围是不是够吧*/
int n,m,cnt,scnt;
stack<int>st;
struct node{
    int to,next;
}edge[M*7];

void addedge(int u,int v,int k)
{
    edge[k].to=v;
    edge[k].next=head[u];
    head[u]=k;
}

void tarjan(int u)
{
    int v,i,min1=dfn[u]=low[u]=cnt++;
    st.push(u);
    for(i=head[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].to;
        if(dfn[v]==-1)tarjan(v);
        min1=min(min1,low[v]);
    }
    if(min1<low[u]){low[u]=min1;return;}
    do
    {
        id[v=st.top()]=scnt;
        st.pop();
        low[v]=n*2;
    }while(v!=u);
    scnt++;
}

void init()
{
    memset(dfn,-1,sizeof(dfn));
    memset(id,-1,sizeof(id));
    memset(head,-1,sizeof(head));
    scnt=cnt=0;
}

int main()
{
    int i,j,a,b,c;
    char op[4];
    scanf("%d%d",&n,&m);
    init();
    int num=0;
    for(i=0;i<m;i++)
    {
        scanf("%d%d%d%s",&a,&b,&c,op);
        if(!strcmp(op,"AND"))
        {
            if(c==1)
            {
                addedge(a,a+n,num++);
                addedge(b,b+n,num++);
                addedge(a+n,b+n,num++);
                addedge(b+n,a+n,num++);
            }
            else
            {
                addedge(a+n,b,num++);
                addedge(b+n,a,num++);
            }
        }
        else
            if(!strcmp(op,"OR"))
            {
                if(c==0)
                {
                    addedge(a+n,a,num++);
                    addedge(b+n,b,num++);
                    addedge(a,b,num++);
                    addedge(b,a,num++);
                }
                else
                {
                    addedge(a,b+n,num++);
                    addedge(b,a+n,num++);
                }
            }
            else
                if(!strcmp(op,"XOR"))
                {
                    if(c==0)
                    {
                        addedge(a,b,num++);
                        addedge(b,a,num++);
                        addedge(a+n,b+n,num++);
                        addedge(b+n,a+n,num++);
                    }
                    else
                    {
                        addedge(a,b+n,num++);
                        addedge(b+n,a,num++);
                        addedge(a+n,b,num++);
                        addedge(b,a+n,num++);
                    }
                }
    }
    int flag=1;
    for(i=0;i<n*2;i++)
        if(dfn[i]==-1)tarjan(i);
    for(i=0;i<n;i++)
    {
        if(id[i]==id[i+n]&&id[i]!=-1)
        {
            flag=0;
            break;
        }
    }
    if(flag)printf("YES\n");
    else printf("NO\n");
    while(!st.empty())st.pop();

    return 0;
}

好奇一下,同样的代码交了两次,一次60多ms一次90多ms......数据自动生成计算量不同  那是不是一次tle  下一次rp好的话直接ac?值得一试  哈哈

抱歉!评论已关闭.