【解題報告】Educational Codeforces Round 15

題目鏈接


A. Maximum Increase(Codeforces 702A)

思路

最暴力的思路是用兩個指針枚舉子串的首尾位置。但是這樣的複雜度是 O(n2) 的,這樣的算法承受不了本題的數據規模,說明這樣枚舉會出現許多重複。假設我們有一段序列 13,14,16,15,17 我們設置首指針爲 1 ,當尾指針枚舉到 4 的時候我們發現 15<16 因此尾指針沒必要再枚舉下去了,而首指針再枚舉 23 也是沒意義的,因爲枚舉出來的區間長度一定沒有 [1,4] 區間大。因此索性設置下一個首指針爲 4 ,而尾指針也可以直接設置爲 5 ,然後繼續枚舉尾指針……重複這個過程直到尾指針枚舉到頭,每次修改頭指針的時候更新要答案。(這個模仿蟲子爬的方法就是《挑戰程序設計競賽》中的“尺取法”)

代碼

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
int n, ans, head, tail, a[maxn];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    ans = 1;
    head = 1;
    tail = 2;
    while(tail <= n) {
        // 如果尾指針能往下走就往下走
        if(a[tail] > a[tail-1]) {
            ans = max(ans, tail++ - head + 1);
        }
        // 否則重置首指針和尾指針
        else {
            head = tail++;
        }
    }
    printf("%d\n", ans);
    return 0;
}

B. Powers of Two(Codeforces 702B)

思路

根據複雜度的提示覺得這題應該是先枚舉一個 i ,然後想辦法瞬間知道 j ,使得 a[i]+a[j]=2x 。由於不同的 2x 只有 30 個(對最大的 a[i] 取對數可知),這使得我們可以在枚舉 i 的情況下再枚舉 2x ,當 a[i]2x 都固定以後,就能在 a 中二分查找到滿足條件的 j 了(前提是先對 a 排序)。

代碼

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e5 + 5, maxNum = 1e9;
int n, cnt, d, ub, lb, Pow[100], a[maxn];
ll ans;

int main() {
    Pow[0] = 1;
    for(cnt = 1; ; cnt++) {
        Pow[cnt] = Pow[cnt-1] << 1;
        if(Pow[cnt] >= maxNum) {
            break;
        }
    }
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= cnt; j++) {
            // d爲a[i]固定後應該找的a[k]
            d = Pow[j] - a[i];
            if(d <= 0) {
                continue;
            }
            // 查找a[k]的下界
            lb = lower_bound(a + i + 1, a + n + 1, d) - a;
            // 查找a[k]的上界
            ub = upper_bound(a + i + 1, a + n + 1, d) - a;
            if(ub - lb > 0) {
                ans += ub - lb;
            }
        }
    }
    printf("%I64d\n", ans);
    return 0;
}

C. Cellular Network(Codeforces 702C)

思路

根據數據量猜想是二分查找半徑(如果直接枚舉,判斷的話複雜度太高)。在二分查找中最多可以以 O(nlog(n)) 的複雜度判斷給定的半徑是否滿足條件。我們來看看是否能在這樣的限制內完成這個任務。首先,當我們確定了半徑以後就能確定 m 個區間。只要我們能夠判斷 n 個點是否全部在這 m 個區間內就能夠完成任務。
問題轉化成是否能在規定的複雜度內判斷 n 個點是否在 m 個區間內。接着,我們得到的區間是凌亂的,因此我們嘗試對其排序看看是否可以簡化問題,結果發現排完序後問題就明朗了。設排序完成後區間爲 [a[0],b[0]][a[1],b[1]],...,[a[m1],b[m1]] (也就是用 a 數組存區間左端點,用 b 數組存區間右端點),設 city[i] 爲第 i 個城市的座標,我們枚舉 i ,並依次檢查這些城市是否在區間集合之外。然後,先二分查找到小於 city[i] 的最大的區間左端點 a[idx] ,然後在 b[0...idx1] 中找一個最大值 Max ,若 Maxcity[i] 還小的話就說明不存在區間包含 city[i] 了。而尋找區間最大值用 RMQ 算法即可(我用的是 ST 表)。這樣判斷半徑是否滿足條件的算法複雜度一共是 (n+m)log(m) ,再加上二分查找半徑的複雜度,在 3 秒的條件下這題就得到圓滿的解決了。
(另外在我朋友的提醒下知道這題有線性的算法,簡單說是用兩個指針分別指向城市和塔,然後移動指針並用離某個城市最近的塔距該城市的距離更新最小半徑。這樣跑出來是 62ms ,而我的是 702ms

代碼

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair <ll, ll> p;
const int maxn = 1e5 + 5, maxm = 1e5 + 5, maxNum = 1e9;
int n, m, l, r, mid, idx, city[maxn], tower[maxm];
p itv[maxm];
ll Max, a[maxm], b[maxm];

// 基於ST表的RMQ部分
struct ST {
    ll st[maxn][32];
    int log2[maxn];
    void initLog(int n) {
        log2[1] = 0;
        for(int i = 2; i <= n; i++) {
            log2[i] = log2[i-1];
            if((1 << log2[i] + 1) == i) {
                ++log2[i];
            }
        }
    }
    void init(int n, ll* a) {
        for(int i = n - 1; i >= 0; i--) {
            st[i][0] = a[i];
            for(int j = 1; (i + (1 << j) - 1) < n; j++) {
                st[i][j] = max(st[i][j-1], st[i+(1<<j-1)][j-1]);
            }
        }
    }
    ll rmq(int l, int r) {
        int len = r - l + 1, k = log2[len];
        return max(st[l][k], st[r-(1<<k)+1][k]);
    }
}o;

// 判斷給定半徑是否滿足條件
bool ok(ll R) {
    // 處理出m個區間區間
    for(int i = 1; i <= m; i++) {
        itv[i] = p(tower[i] - R, tower[i] + R);
    }
    // 對區間以區間左端點爲關鍵字排序
    sort(itv + 1, itv + m + 1);
    for(int i = 1; i <= m; i++) {
        a[i-1] = itv[i].first;
        b[i-1] = itv[i].second;
    }
    // 初始化ST表
    o.init(m, b);
    // 對每個city[i]判斷是否沒有區間包含它
    for(int i = 1; i <= n; i++) {
        idx = lower_bound(a, a + m, (ll)city[i]) - a;
        if(a[idx] == city[i]) {
            continue;
        }
        if(idx == 0) {
            return false;
        }
        Max = o.rmq(0, idx - 1);
        if(Max < city[i]) {
            return false;
        }
    }
    return true;
}

int main() {
    o.initLog(maxm);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &city[i]);
    }
    for(int i = 1; i <= m; i++) {
        scanf("%d", &tower[i]);
    }
    // 二分查找滿足條件的最小半徑
    l = -1;
    r = 2 * maxNum;
    while(r - l > 1) {
        mid = (l + r) >> 1;
        ok(mid) ? r = mid : l = mid;
    }
    printf("%d\n", r);
    return 0;
}

D. Road to Post Office(Codeforces 702D)

思路

這種運動學的題目,無非是用模擬法或公式法解決。首先想到模擬法,應該先對問題分類,當 DK 時直接坐車顯然更好。在其它情況下(先 D=K ,因爲剛開始就在維修站,先坐一次車必然更好),我們應當判斷相同距離 K 下,開車修車和直接走路誰用的時間更短,假設最短時間爲 t ,那麼在接下來的 D/K 個週期內每個週期都用這種方式會更好。最後剩下一段距離不足 K 的路程也是同樣的道理,看看一次修車加一次開車的時間和直接走路的時間哪個更快,並選擇更快的策略走到終點。

代碼

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
ll D, K, A, B, T, t, ans;

int main() {
    cin >> D >> K >> A >> B >> T;
    ans += A * min(D, K);
    D = max(D - K, 0LL);
    // 計算一個週期內的最小運動時間
    t = min(A * K + T, B * K);
    // 將整數倍區間的路程走完
    ans += D / K * t;
    D %= K;
    // 將剩餘的路程走完
    ans += min(A * D + T, B * D);
    cout << ans << endl;
    return 0;
}

(其它題目略)

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