1. 程式人生 > >【解題報告】Codeforces Round #303 (Div. 2)

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

題目連結

A.Toy Cars(Codeforces 545A)

思路

簡單實現題。將表示碰撞結果的矩陣儲存下來,然後檢查每個車輛是否是“good car“即可。

程式碼

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

const int maxn = 110;
bool good;
int n, G[maxn][maxn];
vector <int> v;

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        for
(int j = 1; j <= n; j++) { scanf("%d", &G[i][j]); } good = true; for(int j = 1; j <= n; j++) { if(i != j && G[i][j] % 2) { good = false; } } if(good) v.push_back(i); } printf("%d\n", v.size()); for
(int i = 0; i < v.size(); i++) { printf("%d ", v[i]); } puts(""); return 0; }

B.Equidistant String(Codeforces 545B)

思路

要構造一個與兩個輸入字串的漢明距離都相等的字串,只要從一個字串出發“走過”兩個字串漢明距離的一半就可以了。具體地,假設輸入的串為 ,我們列舉 i ,當 A[i]B[i] 不相同的時候記錄下 i (放在陣列 v 中)。當我們記錄下的 i 的個數 cnt 為奇數時肯定不能構造出我們的目標字串。當 c

nt 為偶數的時候,我們挑選出 v 中的一半的元素,並將與這些元素對應的 A 上的字元修改掉(例如,原來是 1 ,修改後就變成 0 )。修改後的字串就是我們需要的目標串了。

程式碼


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

const int maxn = 1e5 + 5;
char s[maxn], t[maxn];
int n, m, cnt;
vector <int> v;

int main() {
    scanf("%s%s", s, t);
    cnt = 0;
    n = strlen(s);
    for(int i = 0; i < n; i++) {
        if(s[i] != t[i]) {
            v.push_back(i);
            cnt++;
        }
    }
    if(cnt % 2) puts("impossible");
    else {
        m = cnt >> 1;
        for(int i = 0; i < m; i++) {
            s[v[i]] = t[v[i]];
        }
        printf("%s\n", s);
    }
    return 0;
}

C.Woodcutters(Codeforces 545C)

大意

在一條數軸上有若干棵樹。每棵樹有一個高度,問最多能砍多少棵樹,使得沒有倒下的樹碰到其它的樹。

思路

這題最直觀的解法是搜尋。搜尋樹的每一層都表示一次決策,即該層表示的樹是往左倒還是往右倒。但由於 n 的規模太大,這個演算法是不可行的。於是嘗試用記憶化搜尋或動態規劃去優化它。假設我們考慮到第 i 棵樹,並且在前 i 棵樹中,最多可以放倒 d 棵樹。我們發現, d 的取值還依賴最後一棵樹的狀態,於是我們需要記錄 d[i][j] ,表示前 i 棵樹在第 i 棵樹的狀態是j的時候能倒下的最多的樹的數量。然後分三種情況討論:

  • 第i棵樹直立的時候。不論第 i 棵想要直立是可以“不看前一棵樹的臉色”的,因此有 d[i][0]=max{d[i1][j]0j2}
  • 第i棵樹向左倒的時候。第i棵樹想要向左倒,就必須“看前一棵樹的臉色”,也就是看前一棵樹給它留下了多少空位。當前一棵樹沒有向右倒的時候有 d[i][1]=max(d[i1][0],d[i1][1])+1 ,當前一棵樹向右倒的時候有 d[i][1]=max(d[i][1],d[i1][2]+1) 。在實現的時候要注意判斷位置是否足夠讓一棵樹倒下。
  • 第i棵樹向右倒的時候。這種情況的最優值一定比第 i 棵樹直立的時候要多 1 ,因此 d[i][2]=d[i][0]+1

最後的答案就是 d[n][2]

程式碼

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

const int maxn = 1e5 + 10, INF = 1e9;
int n, ans, x[maxn], h[maxn], d[maxn][3];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d%d", &x[i], &h[i]);
    }
    // 設定兩棵虛擬的樹 
    x[0] = - INF;
    x[n+1] = INT_MAX;
    for(int i = 1; i <= n; i++) {
        // 直立 
        d[i][0] = max(max(d[i-1][0], d[i-1][1]), d[i-1][2]);
        // 向左倒
        if(h[i] < x[i] - x[i-1]) {
            d[i][1] = max(d[i-1][0], d[i-1][1]) + 1;
        }
        if(h[i] + h[i-1] < x[i] - x[i-1]) {
            d[i][1] = max(d[i][1], d[i-1][2] + 1);
        }
        // 向右倒
        if(h[i] < x[i+1] - x[i]) {
            d[i][2] = d[i][0] + 1;
        }
    }
    printf("%d\n", d[n][2]);
    return 0;
}

思路

其實本題也不是沒有貪心策略。對於兩端的樹而言,左端的樹往左倒,右端的樹往右倒一定是最優的策略。對於其它的樹(從左向右掃描的話),先考慮向左倒再考慮向右倒一定是最優策略。(證明方法暫時沒想到)

程式碼

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

const int maxn = 1e5 + 10;
int n, ans, x[maxn], h[maxn];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d%d", &x[i], &h[i]);
    }
    ans = 2;
    for(int i = 2; i < n; i++) {
        if(x[i] - x[i-1] > h[i]) {
            ans++;
        }
        else if(x[i+1] - x[i] > h[i]) {
            x[i] += h[i];
            ans++;
        }
    }
    printf("%d\n", n <= 2 ? n : ans);
    return 0;
}

D.Queue(Codeforces 545D)

大意

有若干個人在排隊等待服務,服務員服務每個人的時間都是不同的,對於一個人而言,若他前面的人的被服務總時間大於它自己被服務的時間,他就會感到失望。問最多可以讓多少人感到不失望。

思路

拿到這道題,直覺告訴我應該是先對資料排序(因為感性認知告訴我忍耐值小的人放在前面似乎能夠滿足儘可能多的人)然後再微調(感性認知不一定是事實)。為了方便討論我們用O表示排好序的佇列中不會失望的某個人,用X表示排好序的佇列中會失望的某個人。在微調的過程中,首先 OOO...O 形式的情況下,顯然不應該調整任何人的順序。其次在 OOO...OX 形式的情況下, X 不應該向前調整,應為這樣是沒有任何好處的(為了滿足一個人而讓至少一個人失望)。相反地, X 應該向後調整。既然它必須向後調整,反正它無論如何也得無法不失望了,不如就讓他站到佇列末尾。這樣以來,在他身後的原本所有可能是 O 的人仍然是 O ,原本可能是 X 的人有可能因為他的離開變成 O
所以我們總結出演算法:先對輸入資料排序,然後掃描排序後資料的同時維護字首和 sum 和不會失望的人數 cnt 。掃描經過的每個 O 的值都被加入 sum 中,而 X 因為被加到佇列末尾所以它的值不能加入到 sum 中。這樣,在掃描的過程中我們根據 sum 判斷某個人是否是 O ,然後更新 sumcnt 。最終的 cnt 就是我們要的答案。

程式碼

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

const int maxn = 1e5 + 10;
int n, cnt, sum, a[maxn];

int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    sort(a, a + n);
    cnt = sum = 0;
    for(int i = 0; i < n; i++) {
        if(a[i] >= sum) {
            sum += a[i];
            cnt++;
        }
    }
    printf("%d\n", cnt);
    return 0;
}

E.Paths and Trees(Codefoeces 545E)

大意

在一個圖 G 中找一棵“從 u 出發的最短路樹 T ”,最短路樹的性質是: TG 的生成樹且 u 到樹上任意點 v 的最短距離等於 uv 在原圖中的最短距離。

思路

這個問題同時具有最小生成樹和最短路徑的特點,但最小生成樹無法算出起點到每個點的最短路徑,因此只考慮用後者來解決問題。
假設我們已經在原圖上跑了一遍 Dijkstra 演算法。那麼我們我們就有了 d 陣列記錄下起點到每個點的最短路徑