poj2723详解(二分 + 2-SAT)( 两种方法求解 )

题目大概意思为有 N 对锁(即有 2N 个锁),每对锁只能用其中一种,用了一种后,另一种会消失,有M 个大门,每个门有两个锁,开了其中一个就能打开门,问能从第一个门开始,一个个按顺序打开,最多打开多少门

二选一,有限制条件(每个限制条件中变量个数不超过 2 ),考虑 2-SAT
求的是最大可行解,考虑 二分

第一种:
我们考虑每个门之间的关系,例如有一对钥匙为 1 2 ,而门1 为 0 1,门2 为 0 2,这样如果选了 门1 的 锁1,则 钥匙2 消失,所以我们就必须选择 门2 的 锁0
(我们如果这样考虑每个门之间的关系,预处理连接各个边,这样的代码复杂度为 O( M ^ 2 ),能AC,毕竟 M 才2000多一点,但不是优解)
代码如下:( 820K 110MS )

#include <iostream>
#include <stdio.h>
#include <vector>
#include <string.h>
using namespace std;
vector<int> edge[6005];
vector<int> redge[6005];
vector<int> flag;
int used[6005];
int kind[6005];
int pipei[4005];
int data[3005][2];
int N, M;
void add_edge(int i, int j)
{
    edge[i].push_back(j);
    redge[j].push_back(i);
}
void dfs(int v, int n)
{
    used[v] = true;
    for(int i = 0; i < edge[v].size(); i++)
    {
        if(!used[edge[v][i]] && edge[v][i] % M < n)
        {
            dfs(edge[v][i], n);
        }
    }
    flag.push_back(v);
}
void rdfs(int v, int k, int n)
{
    used[v] = true;
    kind[v] = k;
    for(int i = 0; i < redge[v].size(); i++)
    {
        if(!used[redge[v][i]] && redge[v][i] % M < n)
        {
            rdfs(redge[v][i], k, n);
        }
    }
}
bool pan(int n)
{
    flag.clear();
    memset(used, false, sizeof(used));
    for(int i = 0; i < n; i++)
    {
        if(!used[i]) dfs(i, n);
    }
    for(int i = M; i < M + n; i++)
    {
        if(!used[i]) dfs(i, n);
    }
    memset(used, false, sizeof(used));
    int num = 0;
    for(int i = flag.size() - 1; i >= 0; i--)
    {
        if(!used[flag[i]]) rdfs(flag[i], num++, n);
    }
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        if(kind[i] == kind[i + M])
        {
            return false;
        }
    }
    return true;
}
int main()
{
    while(1)
    {
        scanf("%d %d", &N, &M);
        if(N == 0 && M == 0)
        {
            break;
        }
        for(int i = 0; i < 2 * M; i++)
        {
            edge[i].clear();
            redge[i].clear();
        }
        for(int i = 0; i < N; i++)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            pipei[a] = b;
            pipei[b] = a;
        }
        for(int i = 0; i < M; i++)
        {
            scanf("%d %d", &data[i][0], &data[i][1]);
        }
        for(int i = 0; i < M; i++)
        {
            for(int j = i + 1; j < M; j++)
            {
                if(data[i][0] == pipei[data[j][0]])
                {
                    add_edge(i, j + M);
                    add_edge(j, i + M);
                }
                if(data[i][0] == pipei[data[j][1]])
                {
                    add_edge(i, j);
                    add_edge(j + M, i + M);
                }
                if(data[i][1] == pipei[data[j][0]])
                {
                    add_edge(i + M, j + M);
                    add_edge(j, i);
                }
                if(data[i][1] == pipei[data[j][1]])
                {
                    add_edge(i + M, j);
                    add_edge(j + M, i);
                }
            }
        }
        int l = 1, r = M + 1, mid = (l + r) / 2;
        while(l < r - 1)
        {
            if(pan(mid))
            {
                l = mid;
                mid = (l + r) / 2;
            }
            else
            {
                r = mid;
                mid = (l + r) / 2;
            }
        }
        printf("%d\n", l);
    }
}

第二种:
我们考虑门自身,和钥匙自身的关系。我们每个门的两个锁,必须选其中一个,我们一对钥匙,最多选其中一个,即对于钥匙 ¬ i ∨ ¬ j ,对于门 i ∨ j 。 例如一对钥匙为 1 2 ,门1 为 0 1,则钥匙选了 1 ,就不能选 2,选了 2, 就不能选 1,门 1 没有选 0,则必须选 1,没有选 1,则必须选 0
(这种每次构建边最多只用 O( n ),乘上二分的 O( logn ),代码的复杂度为 O( n logn ),代码当然也可以AC)
代码如下:( 892K 63MS )

#include <iostream>
#include <stdio.h>
#include <vector>
#include <string.h>
using namespace std;
vector<int> edge[10005];
vector<int> redge[10005];
vector<int> flag;
int used[10005];
int kind[10005];
int pipei[10005];
int data[3005][2];
int N, M;
void add_edge(int i, int j)
{
    edge[i].push_back(j);
    redge[j].push_back(i);
}
void dfs(int v)
{
    used[v] = true;
    for(int i = 0; i < edge[v].size(); i++)
    {
        if(!used[edge[v][i]])
        {
            dfs(edge[v][i]);
        }
    }
    flag.push_back(v);
}
void rdfs(int v, int k)
{
    used[v] = true;
    kind[v] = k;
    for(int i = 0; i < redge[v].size(); i++)
    {
        if(!used[redge[v][i]])
        {
            rdfs(redge[v][i], k);
        }
    }
}
bool pan(int n)
{
    flag.clear();
    for(int i = 0; i < 4 * N; i++)
    {
        edge[i].clear();
        redge[i].clear();
    }
    for(int i = 0; i < 2 * N; i++)
    {
        add_edge(i, pipei[i] + 2 * N);
    }
    for(int i = 0; i < n; i++)
    {
        add_edge(data[i][0] + 2 * N, data[i][1]);
        add_edge(data[i][1] + 2 * N, data[i][0]);
    }
    memset(used, false, sizeof(used));
    for(int i = 0; i < 2 * 2 * N; i++)
    {
        if(!used[i]) dfs(i);
    }
    memset(used, false, sizeof(used));
    int num = 0;
    for(int i = flag.size() - 1; i >= 0; i--)
    {
        if(!used[flag[i]]) rdfs(flag[i], num++);
    }
    for(int i = 0; i < 2 * N; i++)
    {
        if(kind[i] == kind[i + 2 * N])
        {
            return false;
        }
    }
    return true;
}
int main()
{
    while(1)
    {
        scanf("%d %d", &N, &M);
        if(N == 0 && M == 0)
        {
            break;
        }
        for(int i = 0; i < N; i++)
        {
            int a, b;
            scanf("%d %d", &a, &b);
            pipei[a] = b;
            pipei[b] = a;
        }
        for(int i = 0; i < M; i++)
        {
            scanf("%d %d", &data[i][0], &data[i][1]);
        }
        int l = 0, r = M + 1, mid = (l + r) / 2;
        while(l < r - 1)
        {
            if(pan(mid))
            {
                l = mid;
                mid = (l + r) / 2;
            }
            else
            {
                r = mid;
                mid = (l + r) / 2;
            }
        }
        printf("%d\n", l);
    }
}

(末尾吐槽,这道题我pipei数组开小了一点点,告诉我超时,搞得我找了一天哪里复杂度高了,无语子!!!!!)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值