HDOJ1815/POJ2749:Building roads (2-SAT)

HDOJ 1815 2-SAT

题目链接HDOJ1815:Building roads

题目大意:
有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=pp

( 3 ) . p → q = ( ! p ) ∨ q (3).p \rightarrow q = (!p) \vee q (3).pq=(!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连接到同一个中转站

xyR
001
010
100
111

根据真值表,因为要写成合取范式,故应该找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;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值