1. 程式人生 > >poj2528 線段樹+離散化

poj2528 線段樹+離散化

題意:n(n<=10000)個人依次貼海報,給出每張海報所貼的範圍li,ri(1<=li<=ri<=10000000)。

      求出最後還能看見多少張海報。

輸入:

1
5
1 4
2 6
8 10
3 4
7 10

解法:離散化,如下面的例子(題目的樣例),因為單位1是一個單位長度,將下面的

      1   2   3   4  6   7   8   10

     —  —  —  —  —  —  —  —

      1   2   3   4  5   6   7   8

離散化  X[1] = 1; X[2] = 2; X[3] = 3; X[4] = 4; X[5] = 6; X[7] = 8; X[8] = 10

於是將一個很大的區間對映到一個較小的區間之中了,然後再對每一張海報依次更新在寬度為1~8的牆上(用線段樹),最後統計不同顏色的段數。

但是隻是這樣簡單的離散化是錯誤的,

如三張海報為:1~10 1~4 6~10

離散化時 X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 6, X[ 4 ] = 10
第一張海報時:牆的1~4被染為1;
第二張海報時:牆的1~2被染為2,3~4仍為1;
第三張海報時:牆的3~4被染為3,1~2仍為2。
最終,第一張海報就顯示被完全覆蓋了,於是輸出2,但實際上明顯不是這樣,正確輸出為3。

新的離散方法為:在相差大於1的數間加一個數,例如在上面1 4 6 10中間加5(演算法中實際上1,4之間,6,10之間都新增了數的)

X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 5, X[ 4 ] = 6, X[ 5 ] = 10

這樣之後,第一次是1~5被染成1;第二次1~2被染成2;第三次4~5被染成3

最終,1~2為2,3為1,4~5為3,於是輸出正確結果3。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

#define M 10005

int m, li[M], ri[M];
int x[M<<3], col[M<<4], ans;
bool hash[M];

void PushDown(int rt) {
     col[rt<<1] = col[rt<<1|1] = col[rt];
     col[rt] = -1;
}

void Update(int L, int R, int c, int l, int r, int rt) {
     if (l >= L && r <= R) {
         col[rt] = c;
         return;
     }

     if (col[rt] != -1) PushDown(rt);
     int m = (l + r) >> 1;
     if (m >= L) Update(L, R, c, l, m, rt<<1);
     if (m < R)  Update(L, R, c, m+1, r, rt<<1|1);
}

void query(int l, int r, int rt) {
    if (l == r) {
        if (!hash[col[rt]]) {
        ans++;
        hash[col[rt]] = true;
       }
       return;
    }
    if (col[rt] != -1) PushDown(rt);
    int m = (l + r) >> 1;
    query(l, m, rt<<1);
    query(m+1, r, rt<<1|1);
}

int BSearch(int ll, int hh, int xx) {
    int mm;

    while (ll <= hh) {
        mm = (ll + hh) >> 1;
        if (x[mm] == xx) return mm;
        else if (x[mm] > xx)  hh = mm - 1;
        else ll = mm + 1;
    }
    return -1;
}

int main()
{
    int t, n, i;

    scanf ("%d", &t);
    while (t--) {
        memset(col, -1, sizeof (col));
        memset (hash, false, sizeof (hash));
        int nn = 0;
        scanf ("%d", &n);
        for (i = 1; i <= n; i++) {
             scanf ("%d %d", &li[i], &ri[i]);
             x[++nn] = li[i];
             x[++nn] = ri[i];
        }
        sort(x+1, x+nn+1);
        m = 1;
        for (i = 2; i <= nn; i++) {
             if (x[i] != x[i-1]) x[++m] = x[i];
        }
        for (i = m; i > 1; i--) {
            if (x[i] - x[i-1] > 1) x[++m] = x[i] - 1;
        }
        sort(x+1, x+m+1);
        for (i = 1; i <= n; i++) {
            int l = BSearch(1, m, li[i]);
            int r = BSearch(1, m, ri[i]);
            Update(l, r, i, 1, m, 1);
        }
        ans = 0;
        query(1, m, 1);
        printf("%d\n", ans);
    }
    return 0;
}


然後再提一下我自己關於二分理解(對二分總是理解不透徹,需要慢慢積累):

最初二分寫成這樣的,老是錯:

int BSearch(int ll, int hh, int xx) {
    int mm;

    while (ll < hh) {
        mm = (ll + hh) >> 1;
        if (x[mm] == xx) return mm;
        else if (x[mm] > xx)  hh = mm;
        else ll = mm + 1;
    }
    return -1;
}
後來理解之後,才發現,這樣寫,只能搜尋在區間[ll,hh)的數,注意,右邊是開區間,而此題正好需要搜尋右邊的閉區間,所以錯了。

之前的思想以及程式碼參考:神牛部落格