離散化

RonChen發表於2024-08-11

例題:P3138 [USACO16FEB] Load Balancing S

因為奶牛肯定在奇數座標上,柵欄必然是偶數座標位置,因此等價於在每頭奶牛 \((x_i,y_i)\) 位置處劃兩條平行於座標軸的直線,將平面分成四個區域,計算每個區域奶牛的數量:

  • 左下:\(0 < x \le x_i, \ 0 < y \le y_i\)
  • 左上:\(0 < x \le x_i, \ y > y_i\)
  • 右下:\(x > x_i, \ 0 < y \le y_i\)
  • 右上:\(x > x_i, \ y > y_i\)

而四個區域的奶牛數量可以藉助二維字首和來計算。

但是,題目中的座標範圍是 \(10^6\),我們沒法開一個 \(10^6 \times 10^6\) 的二維陣列來計算,不管是考慮時間效率還是空間效率都不可行。

注意到 \(n \le 1000\),也就是說最多隻有 \(1000\) 頭奶牛,也就是說座標點的取值最多隻有 \(2000\) 種不同的資料,而實際上我們只需要能夠計算出每個區域的奶牛數量,並不關心座標具體的數值,只要能保持座標值的相對大小關係即可。我們可以在保持原數值之間相對大小關係不變的情況下將其對映成正整數,也就是給每個可能用到的數值按照大小關係分配一個編號,用此編號來代替原數值進行操作。這個過程就稱為離散化。而離散化之後這個二維字首和陣列大小就只有 \(2000 \times 2000\) 級別了,就可以列舉每頭奶牛作為分界點進行計算比較了。

離散化的一種做法是將需要離散化的數值放入一個陣列,對其排序,當需要知道某個原始值經過離散化之後對映成多少時利用二分查詢返回其在有序陣列中的位置即可。

#include <cstdio>
#include <algorithm>
using std::sort;
using std::lower_bound;
using std::max;
using std::min;
const int N = 2005; // 每個座標有兩個數值,離散化之後最多2000個點
int x[N], y[N], d[N], cnt, sum[N][N];
int getid(int num) { // 透過二分查詢獲取離散化之後的值
    return lower_bound(d + 1, d + cnt + 1, num) - d;
}
int main()
{
    int n;
    scanf("%d", &n); cnt = 2 * n;
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &x[i], &y[i]);
        d[i] = x[i]; d[i + n] = y[i];
    }
    sort(d + 1, d + cnt + 1); // 將涉及到的資料排序以便離散化
    for (int i = 1; i <= n; i++) {
        int xid = getid(x[i]), yid = getid(y[i]);
        sum[xid][yid]++;
    }
    // 離散化後預處理二維字首和
    for (int i = 1; i <= cnt; i++)
        for (int j = 1; j <= cnt; j++)
            sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
    int ans = n;
    for (int i = 1; i <= cnt; i++)
        for (int j = 1; j <= cnt; j++) {
            int m = 0;
            int dl = sum[i][j]; m = max(m, dl); // 左下
            int ul = sum[i][cnt] - dl; m = max(m, ul); // 左上 
            int dr = sum[cnt][j] - dl; m = max(m, dr); // 右下
            int ur = n - dl - ul - dr; m = max(m, ur); // 右上
            ans = min(ans, m);
        }
    printf("%d\n", ans);
    return 0;
}

相關文章