HDOJ 1815 2-SAT
题目大意:
有N个农场,和S1,S2两个中转站,现在需要将每个农场和两个中转站之一相连,有两类要求:
1.农场1和农场2必须连在同一个中转站
2.农场1和农场2必须连在不同的中转站
要求:每个农场之间距离的最大值的最小值。
思路:
这种要求极限情况的最优解,很明显可以考虑二分。
设当前情况下农场之间距离的最大值是L,将L进行二分,然后根据L的限制来建图,跑一个2—SAT 来判断解的存在情况。
建图
然后关键来了:
如何进行建图:
对于农场x,我们用逻辑变量来表示他的连接情况:
X : x 连接的是 S1
!X: x 连接的是 S2
此时需要的前置知识点是(参考离散数学教材):
$(1).p \vee q = (!p \rightarrow q) \wedge (!q \rightarrow p) $
( 2 ) . p = p ∨ p (2).p = p \vee p (2).p=p∨p
( 3 ) . p → q = ( ! p ) ∨ q (3).p \rightarrow q = (!p) \vee q (3).p→q=(!p)∨q
这个有什么用呢? 他的作用就在于能够进行将题目的限制条件转换为图中的边。
建边的有几种情况:(网上的博客大多直接跳过该过程,我希望能写一个比较详细的数学推导)
单点的情况:
(1).对于农场 X,如果是连接到S2,则有:
$ !x = (!x \vee !x) = x \rightarrow !x$
然后就可以建边 x -> !x
即:
(2).对于农场 X,如果是连接到S1,则有:
$
x = (x \vee x) = !x \rightarrow x
$
此时就可以建边 !x -> x
双点的情况:
没有严格的限制:
(1).对于农村X,Y,如果必须连接到同一个中转站。
首先我们应该找到当前条件等价的逻辑表达式,然后化简逻辑表达式(使出现蕴涵词 即 ->)来建边。
可以列一个真值表来帮助我们得到逻辑表达式:
x = 1 : X连接到S1(x = 0 即 X连接到S2,下同)
y = 1 :Y连接到S1
R = 1 :X Y连接到同一个中转站
x | y | R |
---|---|---|
0 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
根据真值表,因为要写成合取范式,故应该找R = 0 的一行,可得:(利用前置知识点1)
$
R = (!x \vee y) \wedge (x \vee !y) = (x \rightarrow y) \wedge (!y \rightarrow !x) \wedge (!x \rightarrow !y) \wedge (y \rightarrow x)
$
然后可以建边:
(2).对于农村X,Y,必须连接到不同的中转站:
推导过程同上,最终可建图:
有着严格的限制条件:
当我们有了任意两农场之间距离的最大值L,我们可以凭此建立下列严格的限制条件:
假设农场X到S1的距离是 dis_1[x],到S2的距离是 dis_2[x]
S1 和 S2 之间的距离为 dis_S12
假设此时又农场X,Y因此有以下四种情况:
(1).dis_1[x] + dis_1[y] > L
可知此时 X Y是不能同时连接到 S1的,类似于上面的推导过程,我们可以建边:
(或者直接利用蕴涵词的性质,即: 当X与S1连接,则一定可以推断出Y与S2连接,即 x -> !y)
(2).dis_2[x] + dis_2[y] > L
类似上面可得:
(3).dis_1[x] + dis_2[y] + dis_S12 > L
(4).dis_2[x] + dis_1[y] + dis_S12 > L
然后跑个 2—SAT,判断一下解的存在性即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i(a); i <= (b); ++i)
#define dec(i, a, b) for(int i(a); i >= (b); --i)
#define MP make_pair
const int INF = 0x3f3f3f;
const int N = 10000000 + 10;
const int M = 10000 + 10;
const int Q = 2000 + 10;
const int A = 100 + 10;
struct P{
int v,next;
}G[Q*Q];
struct node{
int x,y;
}Like[Q],Hate[Q],S1,S2,p[Q];
int head[Q],dfn[Q],low[Q],dis_1[Q],dis_2[Q];
int instack[Q],vis[Q],point[Q];
int n,tot,cnt,num,m1,m2,dis_S12;
stack<int> S;
void add(int u,int v){
G[tot].v = v;
G[tot].next = head[u];
head[u] = tot++;
}
int getdis(int i,int j){
return abs(p[i].x-p[j].x) + abs(p[i].y - p[j].y);
}
void init(){
tot = cnt = num = 0;
for(int i=0 ;i<Q ;i++){
head[i] = -1;
dfn[i] = low[i] = vis[i] = 0;
instack[i] = point[i] = 0;
}
while(S.size()) S.pop();
}
void Tarjan(int x){
dfn[x] = low[x] = cnt++;
vis[x] = instack[x] = 1;
S.push(x);
for(int i=head[x] ;i != -1 ;i = G[i].next){
int v = G[i].v;
if(!vis[v]){
Tarjan(v);
low[x] = min(low[x],low[v]);
}
else if(instack[v]){
low[x] = min(low[x],dfn[v]);
}
}
if(low[x] == dfn[x]){
while(1){
int tem = S.top();
S.pop();
instack[tem] = 0;
point[tem] = num;
if(tem == x) break;
}
num++;
}
}
bool check(int L){
init();
for(int i=1 ;i<=n ;i++){
bool flag = false;
if(dis_1[i] > L){
add(i,i+n);
flag = true;
}
if(dis_2[i] > L){
if(flag) return false;
add(i+n,i);
}
}
for(int i=1 ;i<=n ;i++){
for(int j=i+1 ;j<=n ;j++){
if(dis_1[i] + dis_1[j] > L){
add(i,j+n);
add(j,i+n);
}
if(dis_2[i] + dis_2[j] > L){
add(i+n,j);
add(j+n,i);
}
if(dis_1[i] + dis_2[j] + dis_S12 > L){
add(i,j);
add(j+n,i+n);
}
if(dis_2[i] + dis_1[j] + dis_S12 > L){
add(i+n,j+n);
add(j,i);
}
}
}
for(int i=0 ;i<m1 ;i++){
int x = Hate[i].x,y = Hate[i].y;
add(x,y+n);
add(y,x+n);
add(y+n,x);
add(x+n,y);
}
for(int i=0 ;i<m2 ;i++){
int x = Like[i].x,y = Like[i].y;
add(x,y);
add(y+n,x+n);
add(x+n,y+n);
add(y,x);
}
for(int i=1 ;i<=2*n ;i++){
if(!vis[i]){
Tarjan(i);
}
}
for(int i=1 ;i <= n;i++){
if(point[i] == point[i+n])
return false;
}
return true;
}
void solve(){
int left = 0,right = N;
int ans = -1;
while(left <= right){
int mid = (left + right) / 2;
if(check(mid)){
ans = mid;
right = mid - 1;
}
else{
left = mid + 1;
}
}
printf("%d\n",ans);
}
int main(){
while(~scanf("%d%d%d",&n,&m1,&m2)){
scanf("%d%d%d%d",&S1.x,&S1.y,&S2.x,&S2.y);
for(int i=1 ;i<=n ;i++){
scanf("%d%d",&p[i].x,&p[i].y);
dis_1[i] = abs(p[i].x - S1.x) + abs(p[i].y - S1.y);
dis_2[i] = abs(p[i].x - S2.x) + abs(p[i].y - S2.y);
}
for(int i=0 ;i<m1 ;i++){
scanf("%d%d",&Hate[i].x,&Hate[i].y);
}
for(int i=0 ;i<m2 ;i++){
scanf("%d%d",&Like[i].x,&Like[i].y);
}
dis_S12 = abs(S1.x - S2.x) + abs(S1.y - S2.y);
solve();
}
return 0;
}