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;
       }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章