這是我的第一篇博文,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;
}
}