1. 程式人生 > >樹狀陣列+貪心

樹狀陣列+貪心

小黑的樹

Description

小H是個怕黑的人,小H的愛慕者小Z斥巨資給小H在宿舍和實驗室之間修路燈。為了滿足小H的審美,小Z規定只能在整數位置修路燈,並且每個整數位置都只能有一個路燈。在小H的認知裡,有m個路段是非常黑的,所以之少要安裝一定數量的路燈。

小H未來將會是一個貼心的女朋友,她想計算出小Z最少會為她安裝多少路燈。

Input

第一行一個整數T(1<=T<=300),表示測試資料的組數對於每組資料:第一行輸入三個整數n,m,k;(1≤n≤1e6,1≤m≤1e5,1≤k≤n)第二行k個不同整數用空格隔開,表示這些位置已開始就有路燈接下來 m 行表示約束條件。第 i 行三個整數 li,ri,ti 表示:第 i 個區間 [li,ri] 至少要安裝 ti 盞路燈 (1≤li≤ri≤n,1≤ti≤n)。

Output

對於每組資料,輸出 Case x: ans。其中 x 表示測試資料編號(從 1 開始)。如果無解,y 為 −1。

Sample Input 1 

3
5 1 3
1 3 5
2 3 2
5 2 3
1 3 5
2 3 2
3 5 3
5 2 3
1 3 5
2 3 2
4 5 1

Sample Output 1

Case 1: 1
Case 2: 2
Case 3: 1

題解:首先按照區間右端點進行排序,對於一個區間,先用樹狀陣列查詢當前所需區間有多少燈,然後計算還需要need個燈,我們一定是貪心的從右端點r,找need個空白位置,這時候我們利用set位置當前空白位置下標,每個logn查詢。

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e5 + 7;
struct tree //樹狀陣列
{
    int c[maxn];
    int n;
    void init(int _n) {
        n = _n;
        for(int i = 0; i <= n; ++i) c[i] = 0;
    }
    void update(int p, int d) {
        for(int i = p; i <= n; i += i & -i) c[i] += d;
    }
    int query(int p) {
        int res = 0;
        for(int i = p; i > 0; i -= i & -i) res += c[i];
        return res;
    }
    int get(int L, int R) { //查詢L,R有多少個燈
        return query(R) - query(L - 1);
    }
}bit;

struct node
{
    int st, ed, need;
    bool operator < (const node & x) const { //按照區間右端點排序
        if(ed != x.ed) return ed < x.ed;
        else return st < x.st;
    }
}a[maxn];
int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    int T, Case = 0;
    int n, m, k;
    cin >> T;
    while(T --) {
        cin >> n >> m >> k;
        bit.init(++n);
        set<int> pos; //set維護空白位置
        for(int i = 1; i <= n; ++i)  pos.insert(i); //初始都是空白可以放燈的位置
        for(int i = 1; i <= k; ++i) {
            int p;
            cin >> p;
            bit.update(++p, 1);
            pos.erase(p); //將已經放燈的位置進行刪除
        }
        bool fg = 1;
        for(int i = 1; i <= m; ++i) {
            int l, r, t;
            cin >> l >> r >> t;
            a[i].st = l + 1;
            a[i].ed = r + 1;
            a[i].need = t;
            if(t > r - l + 1) fg = 0; //區間需要燈數大於區間長度,無解.(也只有這一種無解的情況)
        }
        cout << "Case " << ++Case << ": ";
        if(!fg) {
            cout << -1 << '\n';
            continue;
        }
        sort(a + 1, a + 1 + m);
        int res = 0;
        for(int i = 1; i <= m; ++i) {
            int cur = bit.get(a[i].st, a[i].ed); //當前區間已經有的燈
            int cnt = a[i].need - cur; //還需要多少燈
            int r = a[i].ed; //我們肯定是貪心從右邊放燈,放燈位置不能超過當前右邊界
//            cout << "i = "<< i << " " << cnt << " r = " << r << endl;
            while(cnt > 0) {
                -- cnt;
                auto it = pos.upper_bound(r); //二分找到大於r的第一個位置.
                -- it; //找到後再對迭代器剪一下,就是小於或等於r的第一個位置
                int p = *it; //解引用
                if(p > r) -- it;
                p = *it;
                bit.update(p, 1); //把這個位置更新成1
                pos.erase(it); //把空白位置刪除
                res ++;
            }
        }
        cout << res << '\n';
    }
    return 0;
}
/*
首先總區間長度n只有1e5
首先排序複雜度m * log(m)
放燈最多放n個燈(最多更新n次),放燈時候每次進行更新log(n),即為n*log(n)
總複雜度 O(m*log(m) + n*log(n))
*/