【解題報告】Educational Codeforces Round 21

題目鏈接


A. Lucky Year(Codeforces 808A)

思路

本題的入手點是,想明白一個正整數只有一個非零位是什麼概念。
一個正整數只有一個非零位,那麼這個數就只有最高位有非零位,也就可以表示成表示成這樣: a10b ,其中 a[1,9]
那麼我們可以設計出這樣的算法:將正整數 n 的最高位增加 1 (如果是 9 的話就增加到 10 ),然後將所有其它爲清零得到一個新的數 k ,最後 kn 就是答案。

代碼

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

typedef long long ll;
string s;
ll k, d;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> s;
    k = s[0] - '0' + 1;
    for(int i = 1; i <= s.size() - 1; i++) {
        k *= 10;
    }
    stringstream ss;
    ss << s;
    ss >> d;
    cout << k - d << endl;
    return 0;
}

B. Average Sleep Time(Codeforces 808B)

思路

本題的入手點在於,熟悉前綴和數組或能夠構造出所需數列。
這道題要求的東西實際上是一個“奇怪的平均值”:“奇怪的和 s ”除以 (nk+1) 。其中 s 爲一個長度爲 k 的滑窗(也就是一個“可以滑動的區間”,其軌跡爲 [1,k],[2,k+1],... )掃過長度爲 n 的數組,在每個狀態中將滑窗中的數求和,再將所有和求和的結果。也就是說,如果我們可以模擬滑窗的前進,並能夠實時高效地查詢滑窗內的和就能夠將本題解決。設 b[i]=limitsij=1a[i] ,也就是設 ba 的前綴和數組,那麼當我們的滑窗走到 [i,i+k1] 這個區間的時候其區間的和爲 b[i+k1]b[i1] 將其累加進答案即可。
當然,除了前綴和外,還可以直接計算答案。直接計算答案的思路是考慮數組中每個元素在 s 中的貢獻度。我們可以先考察 n=7k=3 時各個元素在 s 中的加和次數,將其寫成數列的形式爲:

1,2,3,3,3,2,1

現在根據規律嘗試推廣,於是嘗試求推廣後數列的通項公式設 f(n,k,i) 爲數組長度爲 n ,滑窗大小爲 k 時數列第 i 項的元素,那麼 f(n,k,i)=mink,nk+1,i,ni+1
(以下代碼爲方法 2 的代碼)

代碼

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

const int maxn = 2e5 + 10;
int n, k, a;
double ans;

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> k;
    for(int i = 1; i <= n; i++) {
        cin >> a;
        ans += 1.0 * a * min(min(k, n - k + 1), min(i, n - i + 1));
    }
    cout << setprecision(7) << fixed;
    cout << ans / (n - k + 1) << endl;
    return 0;
}

C. Tea Party(Codeforces 808C)

思路

本題的入手點在於逐步地滿足條件。
首先,我們一定可以給每個杯子倒上一半的水,如果不行的話就無解。那麼接下來只剩下把水壺中的水全部倒出來這一個條件需要滿足了。顯然,可以通過貪心地從大杯子到小杯子依次將杯子倒滿來滿足條件。

代碼

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

typedef pair <int, int> p;
const int maxn = 110;
int n, w, a, idx, cur, tmp, ans[maxn];
p ps[maxn];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> w;
    for(int i = 1; i <= n; i++) {
        cin >> a;
        ps[i] = p(a, i);
    }
    sort(ps + 1, ps + n + 1);
    for(int i = 1; i <= n; i++) {
        a = ps[i].first;
        idx = ps[i].second;
        cur = max(cur, a / 2 + (a % 2 > 0));
        ans[idx] = cur;
        w -= cur;
        if(w < 0) {
            cout << -1 << endl;
            return 0;
        }
        if(i == n && w > 0) {
            ans[idx] += w;
        }
    }
    for(int i = n; i >= 1; i--) {
        a = ps[i].first;
        idx = ps[i].second;
        ans[idx] += tmp;
        if(ans[idx] > a) {
            tmp = ans[idx] - a;
            ans[idx] = a;
        }
        else {
            tmp = 0;
        }
    }
    for(int i = 1; i <= n; i++) {
        cout << ans[i] << ' ';
    }
    return 0;
}

D. Array Division(Codeforces 808D)

思路

本題的入手點在於考察將一個元素移動後會對結果有什麼影響。
假設有下面的數組:

22345

我們將其做一個劃分(用豎線):

2234|5

顯然,將 3 移動到豎線右邊就能使得豎線左右兩邊的和相等。

224|35

可以觀察到,將3 從豎線左邊移動到豎線右邊,使得豎線左邊的數字之和減少了3 ,而右邊增加了3 。也就是說,左右兩邊的和的差值減少了6
那麼顯然,如果已經知道了豎線的位置,同時分別知道了豎線左邊的數字之和s1 ,也知道了豎線右邊的數字之和s2 ,如果豎線左邊存在數字(s1s2)/2 ,或豎線右邊存在數字(s2s1)/2 的話,就表示可以通過將元素右移或左移來使得豎線兩邊之和相等。
所以我們可以預處理出前綴和數組與後綴和數組,枚舉豎線的位置的同時用STLset 維護豎線左右兩邊元素的位置,就可以爲上面提到的算法高效地提供所有所需數據了。

代碼

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

typedef long long ll;
const int maxn = 1e5 + 10;
int n, a[maxn];
ll d, L[maxn], R[maxn];
set <ll> ls, rs;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for(int i = 1; i <= n; i++) {
        L[i] = L[i - 1] + a[i];
    }
    for(int i = n; i >= 1; i--) {
        R[i] = R[i + 1] + a[i];
    }
    for(int i = 1; i <= n; i++) {
        d = R[i + 1] - L[i];
        if(d == 0 || d < 0 && d % 2 == 0 && ls.count(-d / 2)) {
            cout << "YES" << endl;
            return 0;
        }
        ls.insert(a[i]);
    }
    for(int i = n; i >= 1; i--) {
        d = L[i - 1] - R[i];
        if(d == 0 || d < 0 && d % 2 == 0 && rs.count(-d / 2)) {
            cout << "YES" << endl;
            return 0;
        }
        rs.insert(a[i]);
    }
    cout << "NO" << endl;
    return 0;
}

E. Selling Souvenirs(Codeforces 808E)

思路

本題的入手點是,在動態規劃中選用貪心策略。
雖然本題長得很像揹包問題,但是因爲物品數量和揹包容量的乘積太大,所以不能用傳統的方法來做。跟傳統的問題相比,物品的種類只有三種(重量分別爲123 ),這給了我們優化的契機。對於每種物品,我們顯然會先拿價值比較高的。
因此我們可以將三種物品的價值分別放入數組tab[1],tab[2],tab[3] 中,然後分別對這三個數組排序。用tab[1]tab[2] 和動態規劃預處理出d[] 數組,其中d[i] 表示只考慮重量爲12 的物品,並且已經拿了總重爲i 的物品時,能拿到的物品最大價值,在動態規劃的過程中每次要向背包加入某種重量中最大價值的物品。然後對於第三種物品,將其加入揹包的方法是,枚舉結果中拿的第三種物品的數量j ,那麼對於j ,答案就是d[m3j]+sumtab[3](1,j)sum 函數代表對某個數組求區間內元素的和,這裏的意思是對tab[3] 數組的前j個元素求和(可以用前綴和數組優化一下)。
那麼可不可以在動態規劃的過程中考慮三種物品而不是兩種物品呢?答案是可以,但是要考慮的情況就要複雜一些,比如,當要向背包加入重量爲3 的物品時,有可能是加入三件重量爲1 的物品,一件重量爲2 和一件重量爲1 的物品或一件重量爲3 的物品。

代碼

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

typedef long long ll;
const int maxm = 3e5 + 5;
vector <ll> tab[4];
int n, m, w, x, c[3][maxm];
ll ans, d[maxm], sum[maxm];

bool cmp(ll a, ll b) {
    return a > b;
}

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> w >> x;
        tab[w].push_back(x);
    }
    for(int i = 1; i <= 3; i++) {
        sort(tab[i].begin(), tab[i].end(), cmp);
    }
    for(int j = 0; j < m; j++) {
        sum[j + 1] = sum[j];
        if(j < tab[3].size()) {
            sum[j + 1] += tab[3][j];
        }
    }
    if(tab[1].size() > 0) {
        d[1] = tab[1][0];
        c[1][1] = 1;
    }
    for(int i = 2; i <= m; i++) {
        for(int j = 1; j <= 2; j++) {
            if(c[j][i - j] >= tab[j].size()) {
                continue;
            }
            if(d[i] < d[i - j] + tab[j][c[j][i - j]]) {
                d[i] = d[i - j] + tab[j][c[j][i - j]];
                c[j][i] = c[j][i - j] + 1;
                c[j^3][i] = c[j^3][i - j];
            }
        }
    }
    for(int i = 0; i <= m; i++) {
        ans = max(ans, d[i] + sum[(m - i) / 3]);
    }
    cout << ans << endl;
    return 0;
}

(其它題目略)

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