1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AtCoder ABC425 [A-C] 振り返り (C++)

Last updated at Posted at 2025-09-29

AtCoder2回目の挑戦!
今回も解けたのはB問題までで、C問題は解説を見ながらトライしました。
B問題もかなり難しかったように感じました…かなり沼ってしまったので、次回までにBまではすらすら解けるように練習しておきたいです。

問題のリンク

A - Sigma Cubes

問題

1からNまでの数字 i について以下の計算をします。

  1. (-1)^iを求める
  2. i^3を求める
  3. 各項について1と2の積を求め、その和を出力する

解答例

(-1)^iについては必ず1か-1になり、iが偶数か奇数かによって判別できます。
i^3は3回かけるだけです。
for文でiを増やしていって、sumにその和を入れていきましょう。
for文が0から始まるので(i+1)としています。

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

int main(){
    int N; cin >> N;
    int sum = 0;
    for(int i=0; i<N; i++){
        int a = 1;
        //偶数か奇数かを判定
        if((i+1)%2){
            a = -1;
        }
        //3乗を計算し、足し合わせる
        sum += a * (i+1) * (i+1) * (i+1);
    }
    cout << sum;
}

B - Find Permutation 2

問題

1~Nまでの数を1回ずつ使って数列を作り、出力します。
ただし、「-1 -1 2 -1」のように与えられ、-1以外の場所の数字は固定されています。
また、存在しない場合は「No」と出力します。

解答例

順列全探索を使用します。
1~Nの数字を並び替えてできる全パターンを探索すれば答えにたどりくけますし、今回の条件なら全探索してもタイムアウトしません。

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

int main(){
    int N; cin >> N;
    //P:取得した数列を入れる配列
    vector<int> P;
    //Q:全探索用に1~Nを入れる配列
    vector<int> Q;
    bool d = false;
    for(int i=0; i<N; i++){
        int num; cin >> num;
        P.push_back(num);
    }
    for(int i=0; i<N; i++){
        Q.push_back(i+1);
    }
    
    //順列全探索ではdo-while文を使うらしい
    do {
        bool b = true;
        for (int i = 0; i < Q.size(); i++) {
            //一致していない場合、bをfalseにする
            if(P[i] != -1 && P[i] != Q[i]){
                b = false;
            }
        }
        //すべての数字を確かめてもbがtrue=これが回答
        if(b) {
            cout << "Yes" << endl;
            bool first = true;
            for(int x : Q){
                if(!first) cout << " ";
                else first = false;
                cout << x;
            }
            return 0;
        }
    }
    //順列全探索ができる呪文(笑)
    while (next_permutation(Q.begin(), Q.end()));
    
    //回答が見つからなかった場合
    cout << "No" << endl;
}

おまけ

私が本番で提出した回答です。
順列全探索を知らなかったため、気合で作成しました。
参考にしないでください…

一応仕組みを解説します。
Qに-1以外の数字を入れていきます。
Noを出力しないといけないのは、同じ数字が入ってた時なので、まずはそれを検索。
あとは-1の部分に、使える数字numを順番に数字を入れていきます。

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

int main(){
    int N; cin >> N;
    vector<int> v;
    //P:元の数列
    vector<int> P;
    //Q:Pの数字の部分だけ
    vector<int> Q;
    bool d = false;
    for(int i=0; i<N; i++){
        //よく見たら同じ変数名numを使ってて分かりづら過ぎますね。
        //スコープ的に動作はしますが…
        int num; cin >> num;
        P.push_back(num);
        if(num != -1){
            Q.push_back(num);
            d = true;
        }
    }
    //比較しやすいようにQを並び替え
    sort(Q.begin(), Q.end());
    //Qが空だとエラーが起きるので除外
    if(d){
        int i=0;
        //隣り合ってる数が同じでないか確認
        while(i < (Q.size()-1)){
            if(Q[i] == Q[i+1]){
                cout << "No" << endl;
                return 0;
            }
            i++;
        }
    }
    //Pのうち、与えられた数字は使えないため除外。
    //まだ使える数字のみをnumに入れる
    vector<int> num;
    for(int i=0; i<N; i++){
        bool a = true;
        for(int x : Q){
            if(x == i+1){
                a = false;
                break;
            }
        }
        if(a){
            num.push_back(i+1);
        }
    }
    //Pが-1の部分にnumの数字を入れる
    vector<int> ans;
    for(int i=0; i<N; i++){
        if(P[i] == -1){
            ans.push_back(num.back());
            num.pop_back();
        }else{
            ans.push_back(P[i]);
        }
    }
    //答えを出力
    cout << "Yes" << endl;
    bool first = true;
    for(int x : ans){
        if(!first){
            cout << " ";
        }else{
            first = false;
        }
        cout << x;
    }
}

C - Rotate and Sum Query

問題

与えられた数列について、以下の処理を行います。

  • クエリ1:指定回数だけ、先頭の数を末尾に移動
  • クエリ2:指定された部分の和を出力

解答例

クエリ1について、キューとかが使えそうですが、キューではクエリ2が実行できません。
そのため、配列に数字を格納して、先頭の位置frontをメモしておきます。リングバッファみたいなイメージかな?
クエリ1の指定回数だけfrontの位置を後ろにずらせばOKです。
松見まで行ったらfrontを先頭に戻しましょう。

クエリ2について、単純に足すだけに見えますが、毎回足すとタイムアウトします。
そこで、あらかじめ先頭からの和を、配列sumに記録しておきます。
例えば3番目から5番目までの和は、5番目までの和から2番目までの和を引けばいいですね。
ただし、クエリ1で先頭の位置が移動するため、sumは数列の長さNの2倍くらいの長さが必要になります。
また、sumの先頭は0を入れておきましょう。理由は、frontが先頭にある時、front-1が存在しなくならないようにするためです。

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

int main(){
    int N,Q;
    cin >> N >> Q;
    //先頭の位置をメモ
    int front = 0;
    //num:取得した数列
    vector<int> num(N);
    //sum:数列の先頭からの和。
    //長さはNの2倍で、すべて0を入れて初期化。
    //桁が巨大になるのでint型だとオーバーフローしてWAになります
    vector<long long> sum(2*N + 1,0);
    for(int i=0; i<N; i++){
        cin >> num[i];
    }
    for(int i=1; i<N*2+1; i++){
        sum[i] = sum[i-1] + num[(i-1)%N];
    }

    for(int i=0; i<Q; i++){
        int n; cin >> n;
        //クエリ1
        if(n == 1){
            int x; cin >> x;
            //frontが末尾まで来たら先頭に戻すために、剰余 % を使う
            front = (front+x) % N;
        }
        else{//クエリ2
            int y,z; cin >> y >> z;
            long long ans = sum[front + z] - sum[front + y - 1];
            cout << ans << endl;
        }
    }
}

感想

今回も時間内にはBまでしか解けませんでしたが、Bですら1時間くらい沼ったのがダメダメでしたね…
順列全探索、全然知らなかったです…
先週はアルゴリズムの勉強を急いでC問題を数問練習してましたが、今週はB問題をしっかり固める練習をしようと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?