【二分+2-SAT验证】POJ2749[Building roads]题解

15 篇文章 0 订阅
6 篇文章 0 订阅

题目概述

有两个中转点和n个谷仓,每个谷仓只能连向两个中转点的一个。某些谷仓中的牛互相厌恶,不能同时连向同一个中转点,某些谷仓中的牛是朋友,必须同时连向同一个中转点。求一种方案使得谷仓之间的曼哈顿距离的最大值最小。

解题报告

每个谷仓只能连接中转点1(S1)和中转点2(S2)的一个,并且还有很多限制条件,我们不难发现这是2-SAT。但无论是暴力还是Tarjan,都不具备求距离最大值最小的功能,而求最大值最小经常是二分的题目,所以我们想到二分枚举答案mid。
厌恶和朋友的限制比较明显,但我们还需要用到答案mid来进行进一步的限制。由于所有谷仓之间的距离都不大于mid,那么对于两个谷仓i,j有下面四种情况:
1.dis(i,S1)+dis(S1,j)>MAX,意味着i谷仓和j谷仓不能同时选S1。
2.dis(i,S2)+dis(S2,j)>MAX,意味着i谷仓和j谷仓不能同时选S2。
3.dis(i,S1)+dis(S1,S2)+dis(S2,j)>MAX,意味着i谷仓选了S1,j谷仓就不能选S2,且j谷仓选了S2,i谷仓就不能选S1。
4.dis(i,S2)+dis(S2,S1)+dis(S1,j)>MAX,意味着i谷仓选了S2,j谷仓就不能选S1,且j谷仓选了S1,i谷仓就不能选S2。
接下来用Tarjan判断2-SAT是否有解即可。

建边的时候需要仔细,不能有遗漏,否则就会出错。

示例程序

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=500,maxA=1000,maxB=1000,maxm=(maxA+maxB)*4+maxn*maxn*8;

int n,A,B,Sx[2],Sy[2],D;
int x[maxn+5],y[maxn+5],Aa[maxA+5],Ab[maxA+5],Ba[maxB+5],Bb[maxB+5];
int E,lnk[2*maxn+5],nxt[maxm+5],son[maxm+5];
int ti,tot,dfn[2*maxn+5],low[2*maxn+5],SCC[2*maxn+5];
int top,stk[2*maxn+5];bool instk[2*maxn+5];

int absi(int x) {if (x<0) return -x; else return x;}
int getdis(int i,int j) {return absi(x[i]-Sx[j])+absi(y[i]-Sy[j]);}
void Add(int x,int y) {son[++E]=y;nxt[E]=lnk[x];lnk[x]=E;}
void Build(int MAX)
{
    E=0;memset(lnk,0,sizeof(lnk));
    for (int i=1;i<=A;i++)
    {
        Add(Aa[i]<<1,Ab[i]<<1^1);Add(Ab[i]<<1,Aa[i]<<1^1);
        Add(Aa[i]<<1^1,Ab[i]<<1);Add(Ab[i]<<1^1,Aa[i]<<1);
    }
    for (int i=1;i<=B;i++)
    {
        Add(Ba[i]<<1,Bb[i]<<1);Add(Bb[i]<<1^1,Ba[i]<<1^1);
        Add(Ba[i]<<1^1,Bb[i]<<1^1);Add(Bb[i]<<1,Ba[i]<<1);
    }
    for (int i=0;i<n-1;i++)
    for (int j=i+1;j<n;j++)
    {
        if (getdis(i,0)+getdis(j,0)>MAX) Add(i<<1,j<<1^1),Add(j<<1,i<<1^1);
        if (getdis(i,1)+getdis(j,1)>MAX) Add(i<<1^1,j<<1),Add(j<<1^1,i<<1);
        if (getdis(i,0)+D+getdis(j,1)>MAX) Add(i<<1,j<<1),Add(j<<1^1,i<<1^1);
        if (getdis(i,1)+D+getdis(j,0)>MAX) Add(i<<1^1,j<<1^1),Add(j<<1,i<<1);
    }
}
void Tarjan(int x)
{
    dfn[x]=low[x]=++ti;instk[x]=true;stk[++top]=x;
    for (int j=lnk[x];j;j=nxt[j])
        if (!dfn[son[j]]) Tarjan(son[j]),low[x]=min(low[x],low[son[j]]); else
        if (instk[son[j]]) low[x]=min(low[x],dfn[son[j]]);
    if (dfn[x]==low[x])
    {
        int y;tot++;
        do y=stk[top--],instk[y]=false,SCC[y]=tot;
        while (x!=y);
    }
}
bool Two_SAT()
{
    memset(dfn,0,sizeof(dfn));
    for (int i=0;i<2*n;i++) if (!dfn[i]) Tarjan(i);
    for (int i=0;i<2*n;i+=2) if (SCC[i]==SCC[i^1]) return false;
    return true;
}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    scanf("%d%d%d%d%d%d%d",&n,&A,&B,&Sx[0],&Sy[0],&Sx[1],&Sy[1]);
    D=absi(Sx[0]-Sx[1])+absi(Sy[0]-Sy[1]);
    for (int i=0;i<n;i++) scanf("%d%d",&x[i],&y[i]);
    for (int i=1;i<=A;i++) scanf("%d%d",&Aa[i],&Ab[i]),Aa[i]--,Ab[i]--;
    for (int i=1;i<=B;i++) scanf("%d%d",&Ba[i],&Bb[i]),Ba[i]--,Bb[i]--;
    int L=0,R=12000000;
    while (L<=R)
    {
        int mid=L+(R-L>>1);Build(mid);
        if (Two_SAT()) R=mid-1; else L=mid+1;
    }
    if (L>12000000) L=-1;
    return printf("%d\n",L),0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值