POJ 2296 Map Labeler / ZOJ 2493 Map Labeler / HIT 2369 Map Labeler / UVAlive 2973 Map Labeler

POJ 2296 Map Labeler / ZOJ 2493 Map Labeler / HIT 2369 Map Labeler / UVAlive 2973 Map Labeler(2-sat 二分)

Description

Map generation is a difficult task in cartography. A vital part of such task is automatic labeling of the cities in a map; where for each city there is text label to be attached to its location, so that no two labels overlap. In this problem, we are concerned with a simple case of automatic map labeling.

Assume that each city is a point on the plane, and its label is a text bounded in a square with edges parallel to x and y axis. The label of each city should be located such that the city point appears exactly in the middle of the top or bottom edges of the label. In a good labeling, the square labels are all of the same size, and no two labels overlap, although they may share one edge. Figure 1 depicts an example of a good labeling (the texts of the labels are not shown.)

Given the coordinates of all city points on the map as integer values, you are to find the maximum label size (an integer value) such that a good labeling exists for the map.

Input

The first line contains a single integer t (1 <= t <= 10), the number of test cases. Each test case starts with a line containing an integer m (3 ≤ m ≤ 100), the number of cities followed by m lines of data each containing a pair of integers; the first integer (X) is the x and the second one (Y) is the y coordinates of one city on the map (-10000 ≤X, Y≤ 10000). Note that no two cities have the same (x, y) coordinates.

Output

The output will be one line per each test case containing the maximum possible label size (an integer value) for a good labeling.

Sample Input

1
6
1 1
2 3
3 2
4 4
10 4
2 5

Sample Output

2

Http

POJ:https://vjudge.net/problem/POJ-2296
ZOJ:https://vjudge.net/problem/ZOJ-2493
HIT:https://vjudge.net/problem/HIT-2369
UVAlive:https://vjudge.net/problem/UVALive-2973

Source

2-sat 二分

題目大意

在平面直角座標系上有n個點,現在要在這些點之間填充正方形,並且滿足每個點都在正方形的上邊中點或下邊中點。正方形不能重疊,但可以共用一條邊。現在要求出滿足條件的最大邊長正方形的邊長。

解決思路

看到這種最小值最大或是最大值最小的題目,立刻能想到二分。所以我們二分邊長,然後判斷這個邊長是否滿足題意。

那麼現在的關鍵就是:如何判斷某個邊長是否,滿足題意?

通過讀題,我們發現,一個點填充正方形無疑只有兩種情況,這個點在正方形的下邊中點和在正方形的上邊中點。一個點有兩種狀態,所以我們可以用2-sat來判斷可行性。那麼我們定義i表示正方形朝上,i+n表示正方形朝下。爲了方便起見,在下文中,我們用Diff代表當前要判斷的正方形邊長。

那麼如何建圖呢?

我們知道,2-sat中一條有向邊i->j表示選i必須選j,那麼在這個題中,我們就是要找出兩個正方形若一個怎麼樣另一個必須怎麼樣。

首先根據簡單的推理可得,若兩正方形(一下皆用i,j表示,爲了方便敘述,我們假定i的縱座標>=j的縱座標)橫座標之差>=Diff,或是縱座標之差>=2*Diff則兩個正方形無論怎麼擺都不會互相干擾,可以直接跳過。

然後我們分情況討論:
1.縱座標相等的時候(yi==yj)

我們發現這時i與j必然要一個向上一個向下。所以我們連接邊i->j+n(若i向上則j必須向下),連接邊i+n->j(若i向下則j向上),j->i+n,j+n->i(同理)

2.縱座標之差大於0但小於Diff

這時兩個點之間無法擺下一個正方形,所以上面的點必須向上擺,下面的點必須向下擺。

但這樣如何連邊呢?我們連上i+n->i(i若向下則i必須向上),j->j+n(j若向上則j必須向下)。這樣是不是很奇怪?爲什麼麼會有這樣的邊出現?沒錯,這就是刻意製造一種矛盾,使得選i+n時必選i,且在後面判斷可行性的時候若i+n與i在同一強連通分量則不符合題意,無解。這裏就是用來保證i必須向上,而j必須向下。另外,爲了保險起見,還可以連邊i->j+n和j+n->i。

3.縱座標之差大於等於Diff但小於2*Diff

此時,兩個點之間是可以擺下一個正方形的,所以有:若上面的向下,則下面的必須向下;若下面的向上,則上面的必須向上(因爲最多隻能擺下一個正方形)。表述成邊就是:i+n->j+n,j->i。

注意此時並不需要連接i->j或是j+n->i+n,因爲當上面的向上時,下面的既可以向上也可以向下;同理,下面的向下時,上面的既可以向上也可以向下。

建完圖後就是判斷可行性了。這裏推薦的方法是用Tarjan縮點求強聯通分量再看i和i+n是否在同一強連通分量中(若是則說明無解,否則有解)。鑑於前面的博客已經詳細講過如何實現,請到這裏查看。

另一種方法是用暴力染色法,前文也已經敘述過了,可以在這裏查看。

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<stack>
#include<cmath>
using namespace std;

#define mem(Arr,x) memset(Arr,x,sizeof(Arr))

const int maxN=500;
const int inf=2147483647;

int n;
int Readx[maxN];//讀入
int Ready[maxN];
vector<int> E[maxN];//存下2-sat圖
//tajan  //Tarjan算法要用到的變量,不多說
int cnt;
int dfn[maxN];
int low[maxN];
bool instack[maxN];
stack<int> S;
//LTK  //強連通分量
int LTKcnt;
int Belong[maxN];//原圖中的每個點所屬的強連通分量編號

void init();
void Graph_init(int Diff);
void Link(int u,int v);
bool check();
void tarjan(int u);

int main()
{
    int T;
    cin>>T;
    for (int ti=1;ti<=T;ti++)
    {
        cin>>n;
        init();
        for (int i=1;i<=n;i++)
            scanf("%d%d",&Readx[i],&Ready[i]);
        int l=0,r=20000;
        int Ans;
        do//二分答案
        {
            init();
            int mid=(l+r)/2;
            Graph_init(mid);//每次檢查前都要重新初始化
            if (check())//檢查Mid是否滿足
            {
                Ans=mid;
                l=mid+1;
            }
            else
                r=mid;
        }
        while (l<r);
        cout<<Ans<<endl;
    }
    return 0;
}

void init()
{
    cnt=LTKcnt=0;
    mem(dfn,0);
    mem(instack,0);
    for (int i=0;i<=n*2;i++)
        E[i].clear();
    while (!S.empty())
        S.pop();
    return;
}
void Graph_init(int Diff)
{
    for (int i=1;i<=n;i++)
        for (int j=i+1;j<=n;j++)
            if (abs(Readx[i]-Readx[j])<Diff)//i表示往上,i+n表示往下
            {
                int maxy,miny;
                if (Ready[i]>Ready[j])
                {
                    maxy=i;
                    miny=j;
                }
                else
                {
                    maxy=j;
                    miny=i;
                }
                if (Ready[maxy]==Ready[miny])//縱座標相同時,必有一上一下
                {
                    Link(miny,maxy+n);//如果下面的往上,上面的的必須往下
                    Link(maxy,miny+n);//如果上面的往上,下面的的必須往下
                    Link(miny+n,maxy);//同理
                    Link(maxy+n,miny);
                    continue;
                }
                if ((Ready[maxy]-Ready[miny]<2*Diff)&&(Ready[maxy]-Ready[miny]>=Diff))
                {
                    Link(miny,maxy);//如果下面的往上,上面的必須往上
                    Link(maxy+n,miny+n);//如果上面的往下,下面的必須往下
                    continue;
                }
                if ((Ready[maxy]-Ready[miny]<Diff))
                {
                    Link(miny,miny+n);//下面的必須往下
                    Link(maxy+n,maxy);//上面的必須往上
                    Link(miny+n,maxy);//下面的必須往下,上面的必須往上
                    Link(maxy,miny+n);//上面的必須往上,下面的必須往下
                }
            }
    return;
}

void Link(int u,int v)
{
    E[u].push_back(v);
    return;
}

bool check()
{
    for (int i=1;i<=2*n;i++)
        if (dfn[i]==0)
        {
            tarjan(i);
        }
    for (int i=1;i<=n;i++)
        if (Belong[i]==Belong[i+n])//如果i與i+n在同一強連通分量則說明矛盾(一個正方形不可能又往上又往下)
            return 0;
    return 1;
}

void tarjan(int u)//經典的Tarjan過程
{
    int v;

    cnt++;
    dfn[u]=low[u]=cnt;
    instack[u]=1;
    S.push(u);

    for (int i=0;i<E[u].size();i++)
    {
        v=E[u][i];
        if (dfn[v]==0)
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else
            if (instack[v]==1)
                low[u]=min(low[u],dfn[v]);
    }
    if (dfn[u]==low[u])
    {
        LTKcnt++;
        do
        {
            v=S.top();
            S.pop();
            instack[v]=0;
            Belong[v]=LTKcnt;
        }
        while (u!=v);
    }
    return;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章