牛客冬季アルゴリズム基礎訓練キャンプ2 解答解説

問題 A

解法の考え方

入力値7個が全て{1,2,3,5,6}のいずれかであるかを検証する。無効な値が1つでもあれば即時判定する。

コード例

#include <iostream>
using namespace std;

bool isValid(int val) {
    return val == 1 || val == 2 || val == 3 || val == 5 || val == 6;
}

int main() {
    int tmp;
    for (int i = 0; i < 7; i++) {
        cin >> tmp;
        if (!isValid(tmp)) {
            cout << "NO" << endl;
            return 0;
        }
    }
    cout << "YES" << endl;
    return 0;
}

問題 B

解法の考え方

配列を昇順ソート後、中央位置の要素を取得しその値から1を減算した結果を出力する。

コード例

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
    int num;
    cin >> num;
    vector<int> arr(num);
    for (auto &elem : arr) cin >> elem;
    sort(arr.begin(), arr.end());
    cout << arr[num/2] - 1 << endl;
    return 0;
}

問題 C

解法の考え方

長さnの文字列で長さmの部分列を含まない文字列を構築する。周期(n-m)の文字パターンを使用し、条件を満たさない場合は存在不可と判定。

コード例

#include <iostream>
using namespace std;

int main() {
    int cases;
    cin >> cases;
    while (cases--) {
        int n, m;
        cin >> n >> m;
        if (n == m || n - m > 26) {
            cout << "NO" << endl;
            continue;
        }
        cout << "YES" << endl;
        for (int i = 0; i < n; i++) 
            cout << (char)('a' + i % (n - m));
        cout << endl;
    }
}

問題 D

解法の考え方

各文字の最終出現位置を記録し、順方向・逆方向に走査して「次に同じ文字が存在する」最大位置を求める。

コード例

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int calcMaxPos(string s) {
    vector<int> lastPos(26, 0);
    for (int i = 0; i < s.size(); i++)
        lastPos[s[i]-'a'] = i;
    
    int result = 0;
    for (int i = 0; i < s.size(); i++)
        if (lastPos[s[i]-'a'] != i) 
            result = i + 1;
    return result;
}

int main() {
    int len;
    string seq;
    cin >> len >> seq;
    
    int ans = calcMaxPos(seq);
    reverse(seq.begin(), seq.end());
    ans = max(ans, calcMaxPos(seq));
    
    cout << ans << endl;
    return 0;
}

問題 E

解法の考え方

累積和配列を構築し、RMQで区間最小値を効率的に取得。クエリ毎に前処理済みデータから結果を算出。

コード例

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
typedef long long ll;

struct RangeMinQuery {
    vector<vector<ll>> st;
    int sz;
    
    RangeMinQuery(vector<ll> data, int n) : sz(n) {
        st.resize(n+1, vector<ll>(21));
        for (int i=1; i<=n; i++) st[i][0] = data[i];
        for (int j=1; (1<<j) <= n; j++) {
            int w = 1 << j;
            for (int i=1; i+w-1<=n; i++) 
                st[i][j] = min(st[i][j-1], st[i+(1<<(j-1))][j-1]);
        }
    }
    
    ll query(int l, int r) {
        int k = log2(r-l+1);
        return min(st[l][k], st[r-(1<<k)+1][k]);
    }
};

int main() {
    int n, q;
    cin >> n >> q;
    vector<ll> a(n+1), sum(n+1,0);
    for (int i=1; i<=n; i++) {
        cin >> a[i];
        sum[i] = sum[i-1] + a[i];
    }
    
    vector<ll> tmp(n+1);
    for (int i=1; i<=n; i++) 
        tmp[i] = sum[i-1] - a[i];
    
    RangeMinQuery rmq(tmp, n);
    
    while (q--) {
        int l, r;
        cin >> l >> r;
        if (l == r) {
            cout << 0 << endl;
            continue;
        }
        ll base = sum[l-1];
        ll minVal = rmq.query(l+1, r);
        cout << abs(min(0LL, minVal - base)) << endl;
    }
}

問題 F

解法の考え方

ビット単位の分析により(x AND y)+(x OR y)=x+yを証明。条件を満たすペア数は区間長と等しい。

コード例

#include <iostream>
using namespace std;

int main() {
    int tests;
    cin >> tests;
    while (tests--) {
        long long L, R;
        cin >> L >> R;
        cout << R - L + 1 << endl;
    }
    return 0;
}

問題 G

解法の考え方

冪乗計算を反復的に行い、基準値との差分が最小となる指数を探索する。

コード例

#include <iostream>
#include <cmath>
using namespace std;

int main() {
    int tests;
    cin >> tests;
    while (tests--) {
        long long base, mult;
        cin >> base >> mult;
        long long cur = mult;
        int cnt = 1;
        while (abs(base - cur * mult) < abs(base - cur)) {
            cur *= mult;
            cnt++;
        }
        cout << cnt << endl;
    }
}

問題 H

解法の考え方

矩形の辺長を比較し、より長い辺方向に三角形の頂点を配置して最大面積を達成。

コード例

#include <iostream>
using namespace std;

int main() {
    int tests;
    cin >> tests;
    while (tests--) {
        int a,b,c,d;
        cin >> a >> b >> c >> d;
        if (b-a < d-c) {
            cout << a << " " << c << endl;
            cout << a << " " << c+1 << endl;
            cout << a+1 << " " << d << endl;
        }
        else {
            cout << a << " " << d << endl;
            cout << a+1 << " " << d << endl;
            cout << b << " " << d-1 << endl;
        }
    }
}

問題 J

解法の考え方

入力データを指定年月でフィルタリング後、時間帯別にユーザーを分類して重複排除した計数を出力。

コード例

#include <iostream>
#include <vector>
#include <set>
#include <string>
using namespace std;

int main() {
    int n, year, month;
    cin >> n >> year >> month;
    
    vector<set<string>> res(3);
    
    for (int i=0; i<n; i++) {
        string user, date, time;
        cin >> user >> date >> time;
        
        int y = stoi(date.substr(0,4));
        int m = stoi(date.substr(5,2));
        if (y != year || m != month) continue;
        
        int h = stoi(time.substr(0,2));
        int totalSec = h*3600 + stoi(time.substr(3,2))*60 + stoi(time.substr(6,2));
        
        if ((totalSec >= 25200 && totalSec <= 32400) || 
            (totalSec >= 64800 && totalSec <= 72000)) 
            res[0].insert(user);
        else if (totalSec >= 39600 && totalSec <= 46800)
            res[1].insert(user);
        else if (totalSec <= 3600 || totalSec >= 79200)
            res[2].insert(user);
    }
    
    for (int i=0; i<3; i++) 
        cout << res[i].size() << " ";
    cout << endl;
}

問題 K

解法の考え方

グリッド上の連結成分をBFSで探索し、各成分に隣接する空白セルの最小数を算出する。

コード例

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;

const int dx[4] = {1, -1, 0, 0};
const int dy[4] = {0, 0, 1, -1};

int main() {
    int rows, cols;
    cin >> rows >> cols;
    vector<string> grid(rows);
    for (auto &row : grid) cin >> row;
    
    vector<vector<bool>> visited(rows, vector<bool>(cols, false));
    vector<vector<int>> compID(rows, vector<int>(cols, 0));
    int minBlank = INT_MAX;
    int compCount = 0;
    
    for (int i=0; i<rows; i++) {
        for (int j=0; j<cols; j++) {
            if (visited[i][j] || grid[i][j] != '1') continue;
            
            compCount++;
            queue<pair<int,int>> q;
            q.push({i,j});
            visited[i][j] = true;
            int blankCount = 0;
            
            while (!q.empty()) {
                auto [x,y] = q.front();
                q.pop();
                
                for (int k=0; k<4; k++) {
                    int nx = x + dx[k], ny = y + dy[k];
                    if (nx<0 || nx>=rows || ny<0 || ny>=cols) continue;
                    
                    if (grid[nx][ny]=='1' && !visited[nx][ny]) {
                        visited[nx][ny] = true;
                        q.push({nx,ny});
                    }
                    else if (grid[nx][ny]=='0') {
                        if (compID[nx][ny] != compCount) {
                            blankCount++;
                            compID[nx][ny] = compCount;
                        }
                    }
                }
            }
            minBlank = min(minBlank, blankCount);
        }
    }
    cout << minBlank << endl;
}

タグ: シミュレーション ソート 文字列操作 累積和 RMQ

6月30日 18:41 投稿