POJ 2723 Get Luffy Out (2-SAT)

题意:

给你n对钥匙,一共2*n个钥匙。
然后不一定谁和谁配对,但给你这n个配对关系。
一对钥匙里,你只能选一把钥匙。
然后你面前有1扇门,门有两个锁,你只要任意打开一个锁就能进去,然后你面前又有一扇门。。。
给你n扇门各自所需要的锁。现在问你最多你可以过多少扇门。

注意配对关系i,j,只能选其中一个,所以这么建边:
ae(i,j+n) ae(j,i+n) ae(i+n,j) ae(j+n,i)
注意这里我是用i代表选,i+n代表不选。

但是这题要是这么建边无疑是麻烦了一点。。直接用一个数组key将钥匙的对应关系存下来,然后自然就不能同时选了,后边只需要讨论门就好了。

接下来是门:对于当前这个门,可以由i或j钥匙打开,
所以我们可以这么建边:
ae(key[i],j) ae(key[j],i);
代表着选i的对立点(不选i)就得选j,否则打不开门
代表着选j的对立点(不选j)就得选i,否则打不开门

然后跑tarjan判断能否可行。
我在做这题的时候看到别人有的是用二分求的最大门数,然而我觉得二分的话建边有些麻烦就没用(而且建边也会浪费不少操作。。),所以我就加两条边判断一次,不行的话就减减输出。(我一开始忘了在全开门之后减减了。。wa死我了。。2s时间只跑了450ms,所以根本没必要二分啊。。)

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
#include <queue>
#include <cmath>
#define pi acos(-1.0)
#define eps 1e-6
typedef long long int lli;
using namespace std;
const int maxn = 4200;

struct edge{
    int from,to,v,next;
}ed[400000],door[20000];
int head[maxn];
int cnte;
void ae(int x,int y){
    ed[++cnte].to = y;
    ed[cnte].next = head[x];
    head[x]=cnte;
}

int dfn[maxn],low[maxn],vis[maxn],stak[maxn],belong[maxn],cntc,cnts,index;//strong connected component //cnt of stack
void dfs(int u){
    dfn[u]=low[u] = ++index;
    stak[cnts++]=u;
    vis[u]=1;
    for(int i = head[u];i!=-1;i=ed[i].next){
        int v = ed[i].to;
        if(!dfn[v]){
            dfs(v);
            low[u] = min(low[u],low[v]);
        }
        else if(vis[v]){
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        cntc++;int v;
        do{
            v = stak[--cnts];
            vis[v] = 0;
            belong[v] = cntc;
        }while(v!=u);
    }
}
int n,m;
void tarjan(){
    for(int i = 0;i < 2*n;i++){
        if(!dfn[i]){
            dfs(i);
        }
    }
}
int key[4220];
void ini(){
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(belong,0,sizeof(belong));
    cntc=index=0;
}
bool jud(){
    for(int i = 0;i < 2*n;i++){
        if(belong[i]!=0 &&belong[i] == belong[key[i]]){
            return false;
        }
    }
    return true;
}
int main(){
    int t;
    while(~scanf("%d%d",&n,&m),n||m){
        memset(head,-1,sizeof(head));memset(key,0,sizeof(key));
        cnte = 0;
        int a,b;
        for(int i = 1;i <= n;i++){
            scanf("%d%d",&a,&b);
            key[a] = b;key[b] = a;
        }
        for(int i = 1;i <= m;i++){
            scanf("%d%d",&door[i].from,&door[i].to);
        }
        int i;
        for(i = 1;i <= m;i++){
            ae(key[door[i].from],door[i].to);
            ae(key[door[i].to],door[i].from);
            ini();tarjan();
            if(!jud()){
                i--;
                break;
            }
        }
        if(i == m+1) i--;
        printf("%d\n",i);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值