【解題報告】 Educational Codeforces Round 19

題目鏈接


A. k-Factorization(Codeforces 797A)

思路

本題入手的角度是將輸入的 n 看成一系列素數(素因子)的乘積。

  • 因爲素因子已經是因子分解的最小單位了,所以如果 n 的素因子的數量 f 小於 k 的話那麼無論如何都無法表示爲 k 個數相乘的形式。
  • 否則可以將前 k1 個因子構造成前 k1 個數,將第 k 到第 f 個因子的乘積構造成第 k 個數。並且這樣的解必然存在。

代碼

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

vector <int> ans;
int n, k, f, tmp;

// 對n進行因式分解
void primeFactor(int n) {
    for(int i = 2; i * i <= n; i++) {
        while(n % i == 0) {
        // 將素因子儲存起來 
            ans.push_back(i);
            n /= i;
        }
    }
    if(n != 1) {
        ans.push_back(n);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> k;
    primeFactor(n);
    f = ans.size();
    // 判斷無解的情況
    if(f < k) {
        cout << -1 << endl;
        return 0;
    }
    // 輸出前k - 1個數
    for(int i = 0; i < k - 1; i++) {
        cout << ans[i] << ' ';
    }
    tmp = 1;
    for(int i = k - 1; i < f; i++) {
        tmp = tmp * ans[i];
    }
    // 最後一個數由最後幾個素因子合成
    if(tmp > 1) {
        cout << tmp << endl;
    }
    return 0;
}

B. Odd sum(Codeforces 797B)

思路

本題的入手點是探究奇數和的構成。
我們知道,奇數和偶數有這樣的性質。

  • 奇數 + 奇數 = 偶數
  • 奇數 + 偶數 = 奇數
  • 偶數 + 偶數 = 偶數

在本題中,子序列裏偶數的數量是不影響子序列和的奇偶性的。爲了讓子序列的和儘可能大,

我們應該將所有非負偶數都包含在子序列中,負偶數可以捨棄

那麼,子序列中的奇數呢?顯然,子序列中的奇數必須有奇數個。(因爲題目保證一定有解所以不用考慮序列中沒有奇數的情況)所以我們可以

我們可以對所有的奇數從大到小排序,先選最大的奇數加入子序列,從而保證子序列的和一定是奇數。然後成對地向子序列中加入奇數,保證子序列和的奇偶性不變。

代碼

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

vector <int> vec;
int n, m, num, res;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    res = 0;
    for(int i = 1; i <= n; i++) {
        cin >> num;
        // 如果是正奇數或復奇數
        if(num % 2 != 0) {
            vec.push_back(num);
        }
        // 如果是正偶數
        else if(num > 0) {
            res += num;
        }
    }
    m = vec.size();
    // 排序
    sort(vec.begin(), vec.end());
    res += vec[m-1];
    for(int i = m - 2; i > 0; i -= 2) {
    // 負數就不能被加進答案了
        if(vec[i] + vec[i-1] <= 0) {
            break;
        }
        res += (vec[i] + vec[i-1]);
    }
    cout << res << endl;
    return 0;
}

C. Minimal string(Codeforces 797C)

思路

本題的入手點在對 t 這個字符串在題目描述的操作過程中的作用的認識。
因爲字符串 s 的首字符在不斷地被加入 t 的末尾,又從末尾取出,所以我們可以將 t 看成一個棧,其中t的末尾是棧的棧頂。在某次操作之前我們有兩種選擇,最優的策略如下:

  • 當字符串 s 中還有比當前棧頂字符 a 字典序更小的字符 b ,那麼我們應該繼續將 s 中的字符壓棧(將 a 加入 u 的末尾比將 b 加入更優)。
  • 否則將當前棧頂字符 a 加入字符串 u 的末尾,並讓棧彈出棧頂元素。

代碼

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

string str;
stack <char> sta;
queue <char> q;
int n, cur, cnt[300];

// 判斷s中是否還有比ch字典序小的字符
bool isGod(char ch) {
    for(int i = 'a'; i < ch; i++) {
        if(cnt[i] > 0) {
            return false;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> str;
    n = str.size();
    for(int i = 0; i < n; i++) {
        // 統計字符出現次數,供isGod函數使用
        cnt[str[i]]++;
    }
    cur = 0;
    while(cur < n) {
        // 若棧是空棧則壓棧
        if(sta.empty()) {
            sta.push(str[cur]);
            cnt[str[cur]]--;
            cur++;
        }
        // 若當前棧頂字符已是字典序最小
        else if(isGod(sta.top())) {
            q.push(sta.top());
            sta.pop();
        }
        // 否則
        else {
            sta.push(str[cur]);
            cnt[str[cur]]--;
            cur++;
        }
    }
    // 棧內可能還有殘留字符
    while(!sta.empty()) {
        q.push(sta.top());
        sta.pop();
    }
    while(!q.empty()) {
        cout << q.front();
        q.pop();
    }
    return 0;
}

D. Broken BST(Codeforces 797D)

思路

本題的着手點在對 BST 爲何高效的認識。
在查找過程中,每到一個節點, BST 都可以將查找規模縮小一半,這就是 BST 爲何高效的原因。從這句話中我們可以得知的信息是, BST 中每個節點實際上都維護這一個區間,在查找過程中我們可以保證當前查找的值一定在這個節點維護的區間內。
比方說根節點 root 的權值爲 10 。那麼 root 維護的區間爲 [0,INF] ,其左兒子維護的區間爲 [0,9] ,其右兒子維護的區間爲 [11,INF]
於是我們可以用 DFS 序訪問這棵樹,同時維護當前訪問的節點表示的區間。若當前節點的權值不在區間就統計進答案中。

代碼

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

const int maxn = 1e5 + 10, maxv = 1e9;
map <int, int> cnt;
bool hf[maxn];
int v[maxn], l[maxn], r[maxn];
int n, root, ans;

// DFS序遍歷樹的同時維護區間[a, b]
void dfs(int u, int a, int b) {
    if(a > b) {
        return;
    }
    if(a <= v[u] && v[u] <= b) {
        ans += cnt[v[u]];
    }
    if(l[u] >= 0) {
        dfs(l[u], a, min(b, v[u] - 1));
    }
    if(r[u] >= 0) {
        dfs(r[u], max(a, v[u] + 1), b);
    }
}

int main() {
//    freopen("Input2.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> v[i] >> l[i] >> r[i];
        if(l[i] >= 0) {
            hf[l[i]] = true;
        }
        if(r[i] >= 0) {
            hf[r[i]] = true;
        }
        cnt[v[i]]++;
    }
    // 尋找根節點
    for(int i = 1; i <= n; i++) {
        if(hf[i] == false) {
            root = i;
        }
    }
    dfs(root, 0, maxv);
    cout << n - ans << endl;
    return 0;
}

E. Array Queries(Codeforces 797E)

思路

本題的入手點在於分別考慮解決問題的難點,以及巧妙地暴力。

  • 在不考慮 n 的數據規模的情況下,可以根據每個查詢暴力解出答案(用變量的變化模擬 p 的移動)。
  • 在不考慮 k 的數據規模的情況下,可以進行動態規劃。令 d[i][j] 表示當前起始位置爲 ikj 的情況下需要多少步能夠走出數組,那麼按照 i 從大到小的順序 DP 就能得到答案 d[p][k]

但是本題的問題在於 nk 的數據規模必須同時被考慮。在這種情況下,當 k 比較小的時候(比如小於 100 的時候)我們可以動態規劃,當 k 比較大的時候,我們可以暴力。

代碼

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

const int maxn = 1e5 + 10;
int q, p, k, n, a[maxn], d[maxn][110];

// 暴力計算答案
int brute(int p, int k) {
    int res = 0;
    while(p <= n) {
        p += (a[p] + k);
        res++;
    }
    return res;
}

int main() {
//    freopen("Input1.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    // 動態規劃
    for(int i = n; i >= 1; i--) {
        for(int j = 1; j <= 100; j++) {
            if(i + a[i] + j > n) {
                d[i][j] = 1;
            }
            else {
                d[i][j] = d[i+a[i]+j][j] + 1;
            }
        }
    }
    cin >> q;
    while(q--) {
        cin >> p >> k;
        if(k <= 100) {
            cout << d[p][k] << endl;
        }
        else {
            cout << brute(p, k) << endl;
        }
    }
    return 0;
}

F. Mice and Holes(Codeforces 797F)

思路

本題的入手點在於恰當地選擇解決問題的順序,能設計動態規劃算法並使用數據結構進行優化。
在不考慮任何數據規模的情況下,我們顯然可以對每隻老鼠考慮它們在哪個洞中,或對每個洞考慮它收容哪些老鼠。如果用那麼可以用搜索將問題順利解決。
但是顯然我們不能不考慮數據規模,在本題的數據規模下,我們或許能夠利用以上兩種順序中的一個,通過動態規劃加以解決。爲了讓問題更清晰,我們似乎應該對老鼠和老鼠洞按座標排序。
考慮讓 d[i][j] 表示安排了前 i 個老鼠洞,前 j 只老鼠都得到妥善安置的最小的距離和。那麼狀態 (i,j)(i1,j),(i1,j1),,(i1,jc[i])d 值最小的狀態轉移過來(其中 c[i] 表示第 i 個老鼠洞的容量)。於是在動態規劃的同時做 RMQ 即可。

在思考實現方式的過程中要注意以下幾個問題:

  1. 如何用數據結構優化 RMQ (單調隊列)?
  2. 如何快速得知前 j 個老鼠到第 i 個洞的距離之和(預處理)?
  3. 如何優化算法的空間複雜度(滾動數組)?
  4. 累加數據會不會出問題(防溢出)?

代碼

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

typedef long long ll;
typedef pair <ll, int> p;
const int maxn = 5e3 + 5;
const ll INF = 1e13;

// 用結構體表示的單調隊列
struct queue {
    int l, r;
    int idx[maxn];
    ll val[maxn];
    void init() {
        l = 0;
        r = -1;
    }
    // 單調隊列彈出首元素
    void pop(int k) {
        while(r >= l && idx[l] <= k) {
            l++;
        }
    }
    // 單調隊列插入尾元素
    void insert(ll v, int i) {
        while(r >= l && val[r] >= v) {
            r--;
        }
        val[++r] = v;
        idx[r] = i;
    }
    ll getMin() {
        return val[l];
    }
}q;

int n, m, last, cur, mouse[maxn];
ll sum, pre[maxn], d[2][maxn];
p hole[maxn];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> mouse[i];
    }
    for(int i = 1; i <= m; i++) {
        cin >> hole[i].first >> hole[i].second;
        sum += hole[i].second;
    }
    // 若老鼠洞的總容量不夠則無解
    if(sum < n) {
        cout << -1 << endl;
        return 0;
    }
    // 對老鼠和老鼠洞按照座標從小到大排序
    sort(mouse + 1, mouse + n + 1);
    sort(hole + 1, hole + m + 1);
    // DP的初始化
    fill(d[0] + 1, d[0] + n + 1, INF);
    cur = 1;
    for(int i = 1; i <= m; i++) {
        int x = hole[i].first;
        int w = hole[i].second;
        // 預處理距離和
        for(int j = 1; j <= n; j++) {
            pre[j] = pre[j-1] + abs((mouse[j] - x));
        }
        // 初始化單調隊列
        q.init();
        q.insert(0, 0);
        // 計算d[i][j]
        for(int j = 1; j <= n; j++) {
            q.insert(d[last][j] - pre[j], j);
            d[cur][j] = q.getMin() + pre[j];
            q.pop(j - w);
        }
        // 滾動數組
        last = cur;
        cur ^= 1;
    }
    cout << d[last][n] << endl;
    return 0;
}

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