Codeforces Round 1027 (Div. 3) 問題解説(A〜E)

概要

本稿では、Codeforces Round 1027(Div. 3)の問題AからEまでの解法を示す。


問題A:数値構築

問題内容

4桁の整数cが与えられる。整数a(0以上99以下)とb(0以上99以下)を用いて、(a+b)² = c を成立させられるか判定する。

解法

0から√nまでの範囲で遍历し、平方数になるかを判定すればよい。a+b = √c が成立する必要があり、a=0, b=√c で試すだけで十分である。

void solve(){
    int c;
    cin >> c;
    int root = sqrt(c);
    if (root * root == c) {
        cout << 0 << " " << root << endl;
        return;
    }
    
    for (int i = 1; i * i <= c; i++) {
        if (c % i == 0) {
            int d = c / (i * i);
            if (d > i) break;
            if (d * d + i * i == c) {
                cout << i << " " << d << endl;
                return;
            }
        }
    }
    cout << -1 << endl;
}

問題B:文字列とインデックス対

問題内容

長さn(偶数)の01文字列が与えられる。文字列中の文字を適切に並べ替えたとき、ちょうどk個の「良いインデックス対」を作れるか判定する。nは偶数であり、0 ≤ k ≤ n/2 である。

インデックス対の定義: 1 ≤ i ≤ n-i+1 のとき、s[i] == s[n-i+1] であれば (i, n-i+1) はインデックス対である。

解法

文字列をn = 2*k + (n-2*k) に分解する。2*kは条件を満たすインデックス対用、残りの部分是0と1が相殺する部分である。

文字列中の1の個数をcnt1、0の個数をcnt0とする。各自から (n-2*k)/2 を引いた値が負であれば不可。最終的に cnt1/2 + cnt0/2 == k なら可能。

void solve(){
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    
    int cnt1 = 0, cnt0 = 0;
    for (char ch : s) {
        if (ch == '1') cnt1++;
        else cnt0++;
    }
    
    int offset = (n - 2 * k) / 2;
    if (cnt0 < offset || cnt1 < offset) {
        cout << "NO\n";
        return;
    }
    
    cnt0 -= offset;
    cnt1 -= offset;
    
    if (cnt0 / 2 + cnt1 / 2 == k) cout << "YES\n";
    else cout << "NO\n";
}

問題C:配列の分割

問題内容

長さnの非減少数列aが与えられる。要素を削除して、連続しない数列b(b[i] + 1 < b[i+1])を作成する。可能な最大長を求めよ。

解法

連続する要素が取れないため、交互に要素を選択する 전략が有効である。等しい値드는 重複して選べないので、mapを使用して重複を除去する。

各要素について、前回の値が連続している場合はスキップし、連続していない場合は選択する。

void solve(){
    int n;
    cin >> n;
    map exists;
    
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        exists[x] = true;
    }
    
    int result = 0;
    int selected = 0;
    for (auto it = exists.begin(); it != exists.end(); ++it) {
        int val = it->first;
        if (!selected || exists.find(val - 1) == exists.end()) {
            result++;
            selected = 1;
        } else {
            selected = 0;
        }
    }
    cout << result << endl;
}

問題D:最小囲み矩形

問題内容

1e9 × 1e9の格子上にn個の点が与えられる(座標は重複しない)。1つの点を任意の位置に最大1回移動できる。移動後、すべての点を含む矩形の面積の最小値を求めよ。

解法

移動は実質的に1点を削除する動作と同じ。ただし、すべての点が同一線上にある場合は特別に処理が必要である。

削除すべき点は境界にある点だけである。以下の8点を列挙する:

  • x座標最小の2点
  • x座標最大の2点
  • y座標最小の2点
  • y座標最大の2点

各点を削除しながら矩形の面積を計算し、最小値を探す。線上の配置の場合は面積に min(幅, 高さ) を加算する。

struct Point {
    int x, y;
};

void solve(){
    int n;
    cin >> n;
    vector<Point> points(n);
    
    Point minX[2] = {{INF, INF}, {INF, INF}};
    Point maxX[2] = {{-INF, -INF}, {-INF, -INF}};
    Point minY[2] = {{INF, INF}, {INF, INF}};
    Point maxY[2] = {{-INF, -INF}, {-INF, -INF}};
    
    for (int i = 0; i < n; i++) {
        cin >> points[i].x >> points[i].y;
        // 境界点の更新
    }
    
    if (n == 1) {
        cout << 1 << endl;
        return;
    }
    
    long long answer = INF;
    vector<Point> candidates;
    // 8個の候補点を追加
    
    for (Point del : candidates) {
        int left = INF, right = INF, top = -INF, bottom = -INF;
        
        for (Point p : points) {
            if (p.x == del.x && p.y == del.y) continue;
            left = min(left, p.x);
            right = min(right, p.y);
            top = max(top, p.x);
            bottom = max(bottom, p.y);
        }
        
        long long area = 1LL * (top - left + 1) * (bottom - right + 1);
        if (area < n) area += min(top - left + 1, bottom - right + 1);
        answer = min(answer, area);
    }
    cout << answer << endl;
}

問題E:木の最大交互和

問題内容

n個のノードを持つ木が与えられる(根はノード1)。各ノードiには値a[i]が割り当てられている。各ノードについて、根からそのノードまでのパス上の「最大交互和」を求めよ。

交互和とは、符号を交互に変えつつ選択した値の合計の最大値である。

解法

木形DPを使用して解決する。各ノードについて2つの状態を保持する:

  • dp[i][0]: ノードiから根までの最大交互和
  • dp[i][1]: ノードiから根までの最小交互和

遷移式:

dp[i][0] = max(a[i], a[i] - dp[parent[i]][1])
dp[i][1] = max(a[i], a[i] - dp[parent[i]][0])

根では dp[1][0] = 0, dp[1][1] = a[1] とする。DFSで全ノードを巡回しながら計算する。

const long long NEG_INF = -4e18;

void dfs(int u, int parent) {
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if (v == parent) continue;
        
        dp[v][0] = min(a[v] - dp[u][1], (long long)a[v]);
        dp[v][1] = max(a[v] - dp[u][0], (long long)a[v]);
        
        dfs(v, u);
    }
}

void solve(){
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        dp[i][0] = dp[i][1] = NEG_INF;
        head[i] = -1;
    }
    
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        addEdge(u, v);
        addEdge(v, u);
    }
    
    dp[1][0] = 0;
    dp[1][1] = a[1];
    dfs(1, 0);
    
    for (int i = 1; i <= n; i++) {
        cout << max(dp[i][0], dp[i][1]) << " ";
    }
    cout << endl;
}

タグ: codeforces Algorithm competitive-programming data-structures tree-dp

7月19日 20:01 投稿