【解題報告】Codeforces Round #340 (Div. 2)

題目鏈接


A. Elephant(Codeforces 617A)

思路

因爲是求最小行走次數,因此可以貪心地儘可能多走 5 步。其中走 5 步的次數可以通過 x5 求出來。而如果執行除法之後還有剩餘的話( xmod5>0 )行走次數還要加 1 次。

代碼

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

int x;

int main() {
    scanf("%d", &x);
    printf("%d\n", x / 5 + (x % 5 > 0));
    return 0;
}

B. Chocolate(Codeforces 617B)

思路

想象一下,在每兩個 1 之間插入“隔板”就能夠完成任務。那麼有多少種方法呢?設第 i1 和第 i+11 之間有 z[i]0 。那麼在這兩個 1 之間插入“隔板”的方法有 z[i]+1 種。根據計數原理的乘法法則(總方法數等於每個步驟的方法數之積), ans=(z[i]+1)
需要注意的是序列的前綴 0 和尾綴 0z[i] ,是不能計入累乘的。

代碼

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

typedef long long ll;
const int maxn = 105;
int n, head;
ll cnt, ans, sum, a[maxn];

int main() {
    cin >> n;
    for(int i = 0; i < n; i++) {
        cin >> a[i];
        sum += a[i];
    }
    if(sum == 0) {
        puts("0");
        return 0;
    }
    // 找到序列中的第一個1
    for(head = 0; head < n; head++) {
        if(a[head] == 1) {
            break;
        }
    }
    ans = 1;
    // 統計連續的0的個數同時累乘
    for(int i = head; i < n; i++) {
        if(a[i] == 1) {
            ans *= (cnt + 1);
            cnt = 0;
        }
        else {
            cnt++;
        }
    }
    cout << ans << endl;
    return 0;
}

C. Watering Flowers(Codeforces 617C)

思路

顯然圓心和與圓心不重合的點,這兩點能且僅能刻畫一個圓。設有 n 個點,那麼對於圓心 O1 而言,可以通過這 n 個點構成 n 個圓,對於圓心 O2 而言也是如此。
這樣,通過離散化思想我們就可以將問題轉化爲:在 O1 圓集合中選擇一個元素,然後在 O2 圓集合中選擇一個元素,使得這兩個圓能將所有的點覆蓋。
我們可以用 O(n2) 的複雜度來枚舉兩個圓,但是還要額外的複雜度來判斷是否所有點在這兩個圓內。這樣總的複雜度是 O(n3)
考慮是否能讓其中一個圓的集合有序,從而省略判斷。於是我們將點集 P 按照點到 O1 的距離排序。這樣當我們枚舉到 pi 時,所有 pj(j<i) 都已經在圓 O1 中了。然後再從點 pk(k>i) 中找一個到 O2 有最大距離的點構成圓 O2 ,此時圓 O2 就包含了在圓 O1 之外的全部點。這樣總的複雜度是 O(n2)
到此問題圓滿解決了。但實際上還有複雜度更低的算法。我們可以將 r2(i)=max{O2k,k>i} 預處理出來,這樣算上排序總的複雜度是 O(nlogn)

代碼

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

typedef long long ll;

// 點結構體
struct Point {
    ll x, y;
    Point() {}
    Point(ll x, ll y): x(x), y(y) {}
    void input() {
        scanf("%I64d%I64d", &x, &y);
    }
    ll sqrDis(const Point& o) const {
        return (x - o.x) * (x - o.x) + (y - o.y) * (y - o.y);
    }
};

const int maxn = 2010;
int n;
Point f1, f2, a[maxn];
ll ans, Max[maxn];

// 排序用的比較函數
bool cmp(Point a, Point b) {
    return f1.sqrDis(a) < f1.sqrDis(b);
}

int main() {
    scanf("%d", &n);
    f1.input();
    f2.input();
    for(int i = 1; i <= n; i++) {
        a[i].input();
    }
    sort(a + 1, a + n + 1, cmp);
    Max[n+1] = 0;
    // 預處理出同O2距離最遠的點
    for(int i = n; i >= 1; i--) {
        Max[i] = max(f2.sqrDis(a[i]), Max[i+1]);
    }
    ans = Max[1];
    for(int i = 1; i <= n; i++) {
        ans = min(ans, f1.sqrDis(a[i]) + Max[i+1]);
    }
    printf("%I64d\n", ans);
    return 0;
}

D. Polyline(Codeforces 617D)

思路

用筆在草稿紙上嘗試後可以合理地提出假設,答案只有 123 三種。那麼將輸入根據三點的相對位置進行分類即可。

  1. 若三點有某個座標分量兩兩相等,則可以用一個線段覆蓋三個點。
  2. 若三點中有且僅有兩個點 p1,p2 有某個座標分量相等(不妨表示爲 x1=x2 )。且另一個分量滿足 yp3 不在 yp1yp2 之間,則可以用兩個線段覆蓋三個點。
  3. 但以上兩個條件都不滿足的話,就只能用三個線段來覆蓋三個點。

代碼

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

int x[5], y[5];

int solve() {
    // 第一種情況
    if(x[1] == x[2] && x[2] == x[3]) {
        return 1;
    }
    if(y[1] == y[2] && y[2] == y[3]) {
        return 1;
    }
    int a, b, c;
    // 枚舉上述p1,p2點,判斷第二種情況
    for(int i = 1; i <= 3; i++) {
        for(int j = 1; j <= 3; j++) {
            for(int k = 1; k <= 3; k++) {
                if(i == j || j == k || i == k) {
                    continue;
                }
                if(x[i] == x[j]) {
                    a = min(y[i], y[j]);
                    b = max(y[i], y[j]);
                    c = y[k];
                    if(c <= a || c >= b) {
                        return 2;
                    }
                }
                if(y[i] == y[j]) {
                    a = min(x[i], x[j]);
                    b = max(x[i], x[j]);
                    c = x[k];
                    if(c <= a || c >= b) {
                        return 2;
                    }
                }
            }
        }
    }
    // 否則是第三種情況
    return 3;
}

int main() {
    for(int i = 1; i <= 3; i++) {
        cin >> x[i] >> y[i];
    }
    cout << solve() << endl;
    return 0;
}

E. XOR and Favorite Number(Codeforces 617E)

思路

如果對區間和比較敏感的話,求區間的異或和可以先對原數組做預處理,然後就能做到常數時間查詢。因而我們令 b[x]=a[1]a[2]...a[x] 。於是區間 [x,y] 的異或和就能用 b[y]b[x1] 查詢了。
即使做了優化,還是很難用合適的時間複雜度做查詢。於是我們考慮通過小區間的答案推大區間的答案(或者相反)。假設我們已經知道 ans(x,y) (區間 [x,y] 的查詢結果)了。現在想求 ans(x,y+1) ,如果我們要想知道 y+1 位置的元素對答案的貢獻的話,就必須要知道在區間 [x,y] 中有多少前綴和 b[y]k 。爲什麼呢?因爲 b[y]a[x]=k 等價於 b[y]k=a[x] 。於是我們可以用 sum[val] 來維護在當前區間,各個前綴異或和 val 出現的次數。從而 ans(x,y+1)=ans(x,y)+sum[b[y]k]
建立了 ans(x,y)ans(x,y+1) 的關係,就不難建立 ans(x,y)ans(x,y1) , ans(x1,y)ans(x+1,y) 的關係。從而滿足了用莫隊算法解決區間問題的條件。對查詢二元組 (l,r) 按照分塊思想排序後,這個問題就能用暴力移動雙指針法在 O(nm) 的複雜度內解決了。
另外要注意幾個易錯點:

  1. sum 數組的大小至少要開成數組中的數的2倍(因爲取異或值後可能比原數大)。
  2. inserterase 函數中兩條語句的順序不能變(因爲統計的時候都是不計 idx 這個位置的元素的)。
  3. 所有的查詢區間在輸入後 l 要自減 1 (當然也可以在後面的操作中減)。
  4. insert(0) 這條語句不可刪去(例如 k=1a[1]=1 且第一個查詢是 [1,1] 時,如果不加這句的話查詢結果就是 0 )。

代碼

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

typedef long long ll;
const int maxn = 1e5 + 10, maxa = 1e6 + 10;
int n, m, k, size;

// 排序用的查詢結構體
struct query {
    int l, r, idx;
    query() {}
    query(int l, int r, int idx): l(l), r(r), idx(idx) {}
    int block() const {
        return l / size;
    }
    bool operator < (const query& o) const {
        return block() == o.block() ? r < o.r : block() < o.block();
    }
};

// 莫隊算法
struct M {
    ll cnt;
    int a[maxn], sum[maxa<<1];
    ll ans[maxn];
    query q[maxn];
    // 輸入數據
    void input(int n) {
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        for(int i = 2; i <= n; i++) {
            a[i] ^= a[i-1];
        }
    }
    // 向當前區間中加入新元素
    void insert(int idx) {
        cnt += sum[a[idx]^k];
        sum[a[idx]]++;
    }
    // 想當前區間中擦除邊界元素
    void erase(int idx) {
        sum[a[idx]]--;
        cnt -= sum[a[idx]^k];
    }
    void solve(int m) {
        int l, r;
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &l, &r);
            q[i] = query(--l, r, i);
        }
        // 排序
        sort(q + 1, q + m + 1);
        insert(0);
        // 暴力移動指針
        int L = 0, R = 0;
        for(int i = 1; i <= m; i++) {
            l = q[i].l;
            r = q[i].r;
            for(; R < r; insert(++R));
            for(; L > l; insert(--L));
            for(; R > r; erase(R--));
            for(; L < l; erase(L++));
            ans[q[i].idx] = cnt;
        }
    }
    // 輸出答案
    void output(int m) {
        for(int i = 1; i <= m; i++) {
            printf("%I64d\n", ans[i]);
        }
    }
}o;

int main() {
    scanf("%d%d%d", &n, &m, &k);
    size = sqrt(m);
    o.input(n);
    o.solve(m);
    o.output(m);
    return 0;
}

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