题目大概意思为有 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数组开小了一点点,告诉我超时,搞得我找了一天哪里复杂度高了,无语子!!!!!)