Google筆試:Watson and Intervals

問題

這是今年(2016)google校招的筆試題(Round B的C題),難度比acm的低,但是也不簡單。
原題鏈接:http://code.google.com/codejam/contest/5254487/dashboard#s=p2

問題意思是:給你一堆區間,讓你選擇一個來去掉,使得剩下的區間覆蓋到的整數點最少。

比如這些區間是{[2, 5], [3, 5], [4, 7]},
1)去掉[2, 5]的話,剩下的區間覆蓋的整數點爲3,4,5,6,7,有5個;
2)去掉[3, 5]的話,剩下的區間覆蓋的整數點爲2,3,4,5,6,7,有7個;
3)去掉[4, 7]的話,剩下的區間覆蓋的整數點爲2,3,4,5,有4個。
所以很明顯應該去掉最後一個區間,使得剩下的區間覆蓋到的整數點最少。

思路

我的想法是,先算出所有區間覆蓋到的整數點數量,這個很簡單,參考上一篇博客:區間覆蓋與合併

然後,計算每個區間對於整體的獨立貢獻,找出最大的獨立貢獻,刪掉相應的區間,剩下的自然最少了!

維護兩個值:Tail(前面所有區間的右端點的最大值),secondTail(前面所有區間的端點中的第二大的值)。
(secondTail, Tail]這段區間表示還沒被其它區間覆蓋到的!所以想要計算每個區間對於整體的獨立貢獻的話,用這個區間來計算就好了。

舉個例子,[1, 10], [2, 4], [7, 15],Tail和secondTail的初始值爲1-1=0,則:
第一步:[1, 10],Tail變成10,secondTail變成1-1=0(注意(secondTail, Tail]是一個左開右閉區間)。

第二步:[2, 4],此時我們發現2比secondTail大,那麼(secondTail, 2)這段區間就是第一個區間的獨立貢獻了。因爲這段區間,即[1, 1]沒有被任何其它區間覆蓋到。
然後考慮Tail和secondTail的更新,4比10小,所以此時Tail還是10,secondTail呢?應該要變成4!因爲[1, 1]已經計算過了,而[2, 4]被第二個區間佔用了,剩下的就是(4, 10]了!所以secondTail應該是4。

第三步,[7, 15],我們發現7比secondTail大,所以(4, 7)這段區間也是第一個區間的獨立貢獻,沒有別的區間覆蓋到,給它加上。
再考慮Tail和secondTail的更新,15比10大,所以此時Tail應該是15。secondTail呢?應該要變成10!因爲[1, 1]已經計算過了,而[2, 4]被第二個區間佔用了,[5, 6]也被計算過了,[7, 10]被第三個區間佔用了,剩下的就是(10, 15]了!所以secondTail應該是10,並且此時開始,計算獨立貢獻的應該是加到第三個區間上,而不是第一個區間了!

最後的結果,三個區間的獨立貢獻值分別是:3(點1,5,6),0,5(點11,12,13,14,15)。

上面的舉例所說的操作是挺明顯的,不過具體的規則是怎樣的呢?

更新獨立貢獻值

由於我們已經知道,目前還沒被其它區間覆蓋的區間是(secondTail,Tail],那麼對於當前這個區間[F, S],怎麼樣纔會更新獨立貢獻值呢?

如果F比secondTail小會怎樣?那就是覆蓋了(secondTail, S]這一段,並不會有沒有覆蓋到的點可以來更新!

所以應該是F大於secondTail纔會有更新的!(看上面的例子)

我們現在知道secondTail < F,F <= S,而secondTail <= Tail,四者的大小關係不是唯一的,所以分類討論一下:

大小關係 增加的獨立貢獻 secondTail的新值 Tail的新值
secondTail < F <= S <= Tail (secondTail, F) S Tail
secondTail < F <= Tail < S (secondTail, F) Tail S
secondTail <= Tail < F <= S (secondTail, Tail] F S
總結規律 (secondTail, min(Tail, F-1)] 四者中的第二大者 四者中的最大者

更新secondTail和Tail的值

上面討論了有更新時候secondTail和Tail的更新規律,下面就看一下沒有更新時,兩者的更新規律。

此時沒有更新,那麼必然有F <= secondTail,所以四者的大小關係可能是:

大小關係 secondTail的新值 Tail的新值
F <= S <= secondTail <= Tail secondTail Tail
F <= secondTail < S <= Tail S Tail
F <= secondTail <= Tail < S Tail S
總結規律 四者中的第二大者 四者中的最大者

上面這兩張表的更新規則是按照(secondTail, Tail]的意義來決定的,也就是要讓(secondTail, Tail]這段區間保持沒被當前的任何區間覆蓋到!!!

再舉個例子吧,就上表的第二行, F <= secondTail < S <= Tail,很明顯(secondTail, S]這段區間已經被當前區間[F, S]覆蓋到了,所以secondTail應該被更新爲S,再驗證一下(S, Tail]這段區間是不是還沒被其它區間覆蓋到?是滴!

細節

最後再說兩個點,第一,可以發現,上面兩個表中,secondTail和Tail的更新規律都是一樣的,所以可以合併。

第二,如何輕鬆找出四個數字中最大和第二大的數字呢?
排序後選?sb了吧。
這裏只要利用好已知的大小關係,是可以很優雅寫出來的。
我們已知,secondTail <= Tail,並且F <= S。
那麼最大的數字只可能在S和Tail中產生,對不對?

那麼第二大呢?
如果S >= Tail,那麼第二大的數字只能在F和Tail之間產生(因爲肯定不可能是secondTail,Tail比secondTail大)。
否則S < Tail,那麼第二大的數字只能在S和secondTail之間產生(因爲肯定不可能是F,S還比F大啊)。

可以這樣寫:

if (S >= Tail)
    secondTail = max(Tail, F);
else
    secondTail = max(secondTail, S);
Tail = max(Tail, S);

代碼

代碼其實很好寫了,因爲規律都推斷出來了!!!

LL cover(RangeList& intervals) {
    // 注意初始值
    LL Tail = intervals[0].first - 1, secondTail = Tail, TailIndex = -1;
    vector<LL> maxPoints(intervals.size());

    for (int i = 0; i < intervals.size(); ++i) {
        // 更新獨立貢獻值
        if (i > 0 && intervals[i].first > secondTail) {
            maxPoints[TailIndex] += min(Tail, intervals[i].first - 1) - (secondTail + 1) + 1;
        }

        // 更新secondTail值
        if (Tail > intervals[i].second)
            secondTail = max(secondTail, intervals[i].second);
        else
            secondTail = max(Tail, intervals[i].first-1);

        // 更新Tail值
        if (intervals[i].second > Tail) {
            Tail = intervals[i].second;
            TailIndex = i;
        }
    }

    // 別忘了最後還得再算一次!
    maxPoints[TailIndex] += Tail - (secondTail+1) + 1;

    // 找出最大的獨立貢獻值
    LL maxPoint = 0;
    for (int i = 0; i < maxPoints.size(); ++i) {
        maxPoint = max(maxPoint, maxPoints[i]);
    }
    return maxPoint;
}

整道題的代碼是:

#include <iostream>
#include <stdio.h>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <set>
using namespace std;

typedef long long LL;
typedef vector<pair<LL, LL> > RangeList;


LL coverAll(RangeList& intervals) {
    LL left = intervals[0].first, right = intervals[0].second;
    LL area = 0;
    for (int i = 1; i < intervals.size(); ++i) {
        // 前面自成一個區間,那麼就此分開
        if (intervals[i].first > right) {
            area += right - left + 1;
            left = intervals[i].first;
            right = intervals[i].second;
        } else if (intervals[i].second > right) {
            right = intervals[i].second;
        }
    }
    area += right - left + 1;

    return area;
}


LL cover(RangeList& intervals) {
    // 注意初始值
    LL Tail = intervals[0].first - 1, secondTail = Tail, TailIndex = -1;
    vector<LL> maxPoints(intervals.size());

    for (int i = 0; i < intervals.size(); ++i) {
        // 更新獨立貢獻值
        if (i > 0 && intervals[i].first > secondTail) {
            maxPoints[TailIndex] += min(Tail, intervals[i].first - 1) - (secondTail + 1) + 1;
        }

        // 更新secondTail值
        if (Tail > intervals[i].second)
            secondTail = max(secondTail, intervals[i].second);
        else
            secondTail = max(Tail, intervals[i].first-1);

        // 更新Tail值
        if (intervals[i].second > Tail) {
            Tail = intervals[i].second;
            TailIndex = i;
        }
    }

    // 別忘了最後還得再算一次!
    maxPoints[TailIndex] += Tail - (secondTail+1) + 1;

    // 找出最大的獨立貢獻值
    LL maxPoint = 0;
    for (int i = 0; i < maxPoints.size(); ++i) {
        maxPoint = max(maxPoint, maxPoints[i]);
    }
    return maxPoint;
}


int main() {
    int t;
    cin >> t;
    for (int time = 1; time <= t; ++time) {
        LL N, L1, R1, A, B, C1, C2, M;
        cin >> N >> L1 >> R1 >> A >> B >> C1 >> C2 >> M;
        RangeList intervals;
        for (int i = 0, x = L1, y = R1; i < N; ++i) {
            intervals.push_back(make_pair(min(x, y), max(x, y)));
            LL x_last = x, y_last = y;
            x = (A * x_last + B * y_last + C1) % M;
            y = (A * y_last + B * x_last + C2) % M;
        }
        sort(intervals.begin(), intervals.end());
        cout << "Case #" << time << ": " << coverAll(intervals) - cover(intervals) << endl;
    }

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