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

来源:互联网 发布:保障网络和信息安全 编辑:程序博客网 时间:2024/06/13 00:15

题目概述

有两个中转点和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;}
原创粉丝点击