POJ 2296 Map Labeler 2-SAT+二分答案

这是我的第一篇博文,OI省选翻车几乎爆零,于是从OI狗变成了ACMer

听前辈说多写题解、模板、心得丢博客里会有明显的进步,于是决定开始这么做了。

想着第一篇是要写题解还是要写模板,想了想还是写题解。一道比较简单的题。


题目链接:http://poj.org/problem?id=2296


题目大意:地图上有很多的城市,每个城市需要贴上一个标签,标签是正方形,而且标签的上边或者下边的中点是对应的城市。每个标签大小相同而且不能重叠,求最大的标签的边长。

 

看到这种求最大或者最小的题就可以尝试二分答案试试。采用二分法之后问题就转化为了边长为x的标签能否放下。由于每个标签只能在城市的上面或者下面,只有两种状态,很容易就想到了2-SAT算法。

 

考虑每两个城市i,j:

他们之间的横坐标差大于x时,彼此不会产生影响,不需要处理。

他们之间的纵坐标之差大于2x时,也不会互相影响,同样不需要处理。

他们之间的纵坐标差在x和2x之间的时候,他们中间能够放下一个标签,但是不能够放下两个。设他们中间纵坐标大的是large,小的是small,我们可以添加一个条件:large的标签向上or smal的标签向下,这样就能保证中间最多只有一个标签。

他们之间的纵坐标差在(0,x)的时候,他们中间不能放下标签,而且两个点一上一下,那么他们就必须纵坐标大的向上,纵坐标小的向下,我没就能添加两个条件:large的标签向上 or large的标签向上  以及  small的标签向下or  small的标签向下。这样就能保证large的标签一定向上,small的标签一定向下。

特别的,当他们纵坐标相等时,只需要满足一上一下即可,不需要一定哪个向上哪个向下。那么我没可以添加两个条件:i的标签向上or  j的标签向上  以及 i的标签向下 or j的标签向下。

 

添加完条件之后,我们只需要跑一遍2-SAT就能够知道边长为x的标签满不满足了。整个算法的时间复杂度是O(O(2-SAT)*logT)的,其中我的O(2-SAT)是O(mn),m是条件的个数,也就是n^2的,总的时间复杂度是O(n^3logT),这样就能够过了。

 

AC代码:

 

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <cstring>
#define MAXN 105
using namespace std;
 
struct TwoSAT
{
       int n;//个数
       vector<int> G[MAXN*2];//边
       bool mark[MAXN*2];//标记
       int S[MAXN*2],c;//记录回溯/初始化路线
      
       bool dfs(int x)//标记mark[x]进行搜索
       {
              if(mark[x^1]) return false;//与x相反的被标记
              if(mark[x]) return true;//x已经被标记,直接返回成功
              mark[x]=true;//标记
              S[c++]=x;//记录路径
              for(int i=0;i<G[x].size();i++)
                    if(!dfs(G[x][i])) return false;//深搜
              return true;
       }
      
       void init(int n)
       {
              this->n=n;
              for(int i=0;i<n*2;i++) G[i].clear();
              memset(mark,0,sizeof(mark));
       }
      
       //x=xval or y=yval
       void add_clause(int x,int xval,int y,int yval)
       {
              x=x*2+xval;//具体x位置
              y=y*2+yval;
              G[x^1].push_back(y);//有向边x^1到y
              G[y^1].push_back(x);
       }
      
       bool solve()
       {
              for(int i=0;i<n*2;i+=2)
                    if(!mark[i] && !mark[i^1])//需要确定值
                    {
                           c=0;
                           if(!dfs(i))//i为true不行
                           {
                                  while(c>0) mark[S[--c]]=false;//回溯/初始化
                                  //如果是while(c>=0) mark[c--]=false的话,最后c的值是-1,不能初始化
                                  if(!dfs(i+1)) return false;//i为真假都不行
                           }
                    }
              return true;
       }
};
int m;
int px[MAXN];
int py[MAXN];
TwoSAT u;
 
int del(int x)
{
       return x>0?x:-x;
}
 
void pre(int diff)//0是向上,1是向下出现正方形
{
       int i,j,large,small;
       for(i=0;i<m;i++)
       for(j=0;j<i;j++)
       {
              if(del(px[i]-px[j])<diff)//互相会干扰
              {
                    if(py[i]>py[j])
                           large=i,small=j;
                    else large=j,small=i;
                   
                    if(py[large]==py[small])//1上一下
                    {
                           u.add_clause(i,0,j,0);//必有1向上
                           u.add_clause(i,1,j,1);//必有1向下
                           continue;
                    }
                   
                    if(py[large]-py[small]<diff*2)//中间可以有一个
                    {
                           if(py[large]-py[small]<diff)//中间不能有(大的向上、小的向下
                           {
                                  u.add_clause(large,0,large,0);//large向上或large向上(large必向上
                                  u.add_clause(small,1,small,1);//small向下或small向下(small必向下
                           }
                           else
                           {
                                  u.add_clause(large,0,small,1);//large向上或small向下(中间最多有一个
                           }
                    }
              }
       }
}
 
int main()
{
       int T;
       cin>>T;
       while(T--)
       {
              cin>>m;
              for(int i=0;i<m;i++)
                    scanf("%d%d",&px[i],&py[i]);
              int l=0,r=20000,mid;
              while(r-l>1)//如果是l==r,样例死循环于l=1 r=2
              {
                    mid=(l+r)/2;
                    u.init(m);
                    pre(mid);
                    if(u.solve()) l=mid;
                    else r=mid-1;
              }
              if(l>r) cout<<"Error!"<<endl;
              u.init(m);
              pre(r);
              if(u.solve()) cout<<r<<endl;
              else cout<<l<<endl;
       }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值