您的位置:首页 > 其它

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

2016-07-29 22:15 357 查看
这是我的第一篇博文,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=yv
a647
al
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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: