【第2回】はじめてのAtCoderとC++(2)

関数・ループ・配列など、競プロでよく使われるより実践的なC++の使い方を紹介します。

Homeブログ一覧【第2回】はじめてのAtCoderとC++(2)

STL の関数

  • c++で用意されている、関数等がまとまっているのを STL(Standard Template Library) と呼びます。
  • 関数名(引数1,引数2,...)という形で呼び出します。
関数機能
min(a,b)a,b のうち小さい方の値を返す
max(a,b)a,b のうち大きい方の値を返す
abs(a)a の絶対値を返す
swap(a,b)変数 a,変数 b の値を交換する
sort(vec.begin(), vec.end())配列変数 vec をソートする(要素を小さい順に並び変える)
reverse(vec.begin(), vec.end())配列変数 vec の要素の並びを逆にする
#include <bits/stdc++.h>
using namespace std;

int main()
{
    int a = 10, b = 5, c = -7;
    cout << min(a, b) << endl; // 5  引数は同じ型の変数でなければならない
    cout << max(a, b) << endl; // 10 引数は同じ型の変数でなければならない
    cout << abs(c) << endl; // 7
    cout << a << " " << c << endl; // 10 -7
    swap(a, c);
    cout << a << " " << c << endl; // -7 10

    vector<int> vec={3,1,2};
    for(int i=0; i<vec.size();i++) cout << vec[i] << " ";  // 3 1 2
    cout << endl;

    reverse(vec.begin(), vec.end());
    for(int i=0; i<=vec.size();i++) cout << vec[i] << " ";  // 2 1 3
    cout << endl;

    sort(vec.begin(),vec.end());
    for(int i=0; i<=vec.size();i++) cout << vec[i] << " ";  // 1 2 3
    cout << endl;
}

問題

解答例

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

int main(){
    int a,b,c;
    cin>>a>>b>>c;
    int ans = max(a,max(b,c))-min(a,min(b,c)); //max(a,b,c)はできない
    // int ans = max({a,b,c})-min({a,b,c});  // これはできる
    cout<<ans<<endl;
}

関数

STL の関数は、c++で用意されている関数ですが、自分で関数を作ることもできます。

  • 関数の定義は戻り値の型 関数名(引数1,引数2,...){ 処理 }という形で行います。
  • 関数の返り値はreturn 返り値で指定します。
  • 関数の返り値がない場合は、voidという型を指定し、return 文はreturn;と書きます。
  • 処理が return 文に到達すると、関数の処理は終了します。
  • 引数に渡された値は基本的にコピーされる
  • このコピーして渡すことを値渡しと呼びます
#include <bits/stdc++.h>
using namespace std;

int sum(int a, int b){  // a,bを引数とする関数, 返り値はint型
    return a+b;
}

void Swap(int a, int b)  // 返り値がない場合はvoid型を指定
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a = 10, b = 5;
    cout << sum(a, b) << endl; // 15

    cout << "Before Swap: " << a << " " << b << endl;  // 10 5
    Swap(a,b);
    cout << "After Swap: " << a << " " << b << endl;  // 10 5

    int temp = a;
    a = b;
    b = temp;
    cout << "After Swap: " << a << " " << b << endl;  // 5 10
}

このように、関数の引数に渡された値は、基本的にコピーされるため、関数内での処理は、関数外の変数に影響を与えないことに注意してください。これを解決するためには、参照渡しを使います。

参照渡し

関数の引数に&をつけると、参照渡しになります。参照渡しを使うと、関数に渡された引数自体を変更することができます。

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

void Swap(int &a, int &b)  // 返り値がない場合はvoid型を指定
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a = 10, b = 5;
    cout << "Before Swap: " << a << " " << b << endl;  // 10 5
    Swap(a,b);
    cout << "After Swap: " << a << " " << b << endl;  // 5 10
}

問題

今回の問題は、途中までプログラムが書いてあるものになります。関数を作成して、プログラムを完成させてください。 関数を定義せずに、プログラムを書くこともできます。その場合は、main 関数の中に、関数の処理を書いてください。

解答例

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

int sum(vector<int> score){  // vector<int>を受け取り、合計点を返す関数
    int ans=0;
    for(int i=0; i<score.size();i++) ans+=score[i];
    return ans;
}

void output(int sum_a,int sum_b,int sum_c){   // 三人の合計点を受け取り、出力する関数
    cout<<sum_a*sum_b*sum_c<<endl;
}

vector<int> input(int N){  // 入力を受け取る関数
    vector<int> vec(N);
    for(int i=0; i<N;i++) cin>>vec[i];
    return vec;
}

int main(){
    int N;
    cin>>N;
    vector<int> A=input(N);
    vector<int> B=input(N);
    vector<int> C=input(N);
    output(sum(A),sum(B),sum(C));
}

関数を定義せずに、プログラムを書く場合は、以下のようになります。

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

int main(){
    int N;
    cin>>N;

    vector<int> A(N);
    vector<int> B(N);
    vector<int> C(N);

    for(int i=0; i<N;i++) cin>>A[i];  // 入力を受け取る
    for(int i=0; i<N;i++) cin>>B[i];
    for(int i=0; i<N;i++) cin>>C[i];

    int sum_a=0,sum_b=0,sum_c=0;
    for(int i=0; i<N;i++) sum_a+=A[i]; // 合計点を計算する
    for(int i=0; i<N;i++) sum_b+=B[i];
    for(int i=0; i<N;i++) sum_c+=C[i];

    cout<<sum_a*sum_b*sum_c<<endl; // 出力する
}

範囲 for 文,マクロ

範囲 for 文

配列のすべての要素に対して同じ処理を行いたいときはfor文を使うと便利です。例えば、以下のように書くことができます。

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

int main(){
    vector<int> vec={1,2,3,4,5};
    for(int i=0; i<vec.size();i++) cout<<vec[i]<<endl;
}

このfor文を簡潔に書く方法がc++にはあり、それを範囲for文と言います。

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

int main(){
    vector<int> vec={1,2,3,4,5};
    for(int x:vec) cout<<x<<endl;
}

これは、vecの要素を順番にxに代入していくという意味になります。 つまり、以下のように書いたのと同じことになります。

for(int i=0; i<vec.size();i++){
    int x=vec[i];
    cout<<x<<endl;
}

この範囲for文は、string型の文字列にも使うことができます。

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

int main(){
    string str="Hello";
    for(char c:str){ // strの要素を順番にcに代入していく
        if(c=='H') cout<<"H"<<endl;
        else cout<<"Not H"<<endl;
    }
}

マクロ

for文って結構書くのが面倒ですよね。そこで、マクロというものを使います。 マクロとは、プログラムの中で定義した名前に対して、あらかじめ決められた処理を行うものです。 マクロを使うと、for文を以下のように書くことができます。

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

#define rep(i,n) for(int i=0; i<(n);i++) // rep(i,n)と書くと、
                                       // for(int i=0; i<(n);i++)と同じ意味になる
int main(){
    vector<int> vec={1,2,3,4,5};
    rep(i,vec.size()) cout<<vec[i]<<endl;
}

問題

今回の問題はfor文になれる問題なので、範囲for文やマクロを実際に使ってみてください。 https://atcoder.jp/contests/apg4b/tasks/APG4b_cg

解答例1

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

int main(){
    vector<int> a(5);
    for(int i=0; i<5;i++) cin>>a[i];
    for(int i=0; i<4;i++) {
        if(a[i]==a[i+1]){
            cout<<"YES"<<endl;
            return 0;
        }
    }
    cout<<"NO"<<endl;
}

解答例2

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i=0; i<(n);i++)

int main(){
    vector<int> a(5);
    rep(i,5) cin>>a[i];
    rep(i,4) {
        if(a[i]==a[i+1]){
            cout<<"YES"<<endl;
            return 0;
        }
    }
    cout<<"NO"<<endl;
}

解答例3

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

int main(){
    vector<int> a(5);
    for(int &x:a) cin>>x;
    for(int i=0; i<4;i++) {
        if(a[i]==a[i+1]){
            cout<<"YES"<<endl;
            return 0;
        }
    }
    cout<<"NO"<<endl;
}

多重ループ

for文の中にfor文をさらに書くと、多重ループとなります。

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

int main(){
    for(int i=0; i<3;i++){
        for(int j=0; j<3;j++){ // iのループの中にjのループがある
            cout<<"i:"<<i<<","<<"j:"<<j<<endl;
        }
    }
}

実行結果

i:0,j:0
i:0,j:1
i:0,j:2
i:1,j:0
i:1,j:1
i:1,j:2
i:2,j:0
i:2,j:1
i:2,j:2

多重ループの時のbreakとcontinue

多重ループの時も、breakとcontinueを使うことができます。多重ループでは、外側のループと内側のループがネスト(入れ子になっている)されています。

braek文

break文は現在のループを終了し、次のループ(外側のループ)に移行します。break文が内側のループにある場合は、内側のループのみを終了します。

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

#define rep(i,n) for(int i=0; i<(n);i++)

int main(){
    rep(i,3){
        rep(j,4){
            if(j==2) break;  // jのループを抜ける
            cout<<"i:"<<i<<","<<"j:"<<j<<endl;
        }
    }
}

実行結果

i:0,j:0
i:0,j:1
i:1,j:0
i:1,j:1
i:2,j:0
i:2,j:1

continue文

continue文は、現在のループの残りの処理をスキップし、次のループに移行します。continue文が内側のループにある場合は、内側のループのみをスキップします。

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

#define rep(i,n) for(int i=0; i<(n);i++)

int main(){
    rep(i,3){
        rep(j,4){
            if(j==2) continue;  // jのループをスキップする
            cout<<"i:"<<i<<","<<"j:"<<j<<endl;
        }
    }
}

実行結果

i:0,j:0
i:0,j:1
i:0,j:3
i:1,j:0
i:1,j:1
i:1,j:3
i:2,j:0
i:2,j:1
i:2,j:3

問題

解答例

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i=0; i<(n);i++)

int main(){
    int N,S;
    cin>>N>>S;
    vector<int> A(N),P(N);
    rep(i,n) cin>>A[i];
    rep(i,n) cin>>P[i];

    int ans=0;
    rep(i,N){  // 全通り試す
        rep(j,N){
            if(A[i]+P[j]==S) ans++;
        }
    }
    cout<<ans<<endl;
}

多次元配列

多次元配列は、配列の中に配列を入れることで実現できます。初めは2次元配列までしか使わないので、2次元配列まで覚えれば大丈夫です。

  • 2次元配列は2次元配列を扱うのに便利です。
  • 宣言の仕方は、vector<vector<型>> vec(要素数1,vector<型>(要素数2,初期値))です。
  • 初期値は省略化可能です。
  • 2次元配列の要素へのアクセスは、vec.at(i).at(j),またはvec[i][j]です。
  • 縦の大きさをvec.size()、横の大きさをvec.at(0).size()で取得できます。

例題

入力

1 3 2 4
9 8 6 0
1 4 2 5

縦3行、横4列の2次元配列し、入力を受け取り偶数がいくつあるかを出力するプログラムを作成してください。

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

#define rep(i,n) for(int i=0; i<(n);i++)

int main(){
    vector<vector<int>> data(3,vector<int>(4));

    rep(i,3) rep(j,4) cin>>data[i][j]; // cin>>data.at(i).at(j); でも可

    int count=0;
    rep(i,3) rep(j,4) if(data[i][j]%2==0) count++;
    cout<<count<<endl;
}

問題

解答例

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (int i = 0; i < (n); i++)

int main()
{
    int n,m;
    cin>>n>>m;
    vector<int> a(m),b(m);
    rep(i,m) cin>>a[i]>>b[i];

    vector<vector<char>> result(n,vector<char>(n,'-'));
    rep(i,m){  // 試合結果を反映
        result[a[i]-1][b[i]-1]='o';
        result[b[i]-1][a[i]-1]='x';
    }
    rep(i,n){
        rep(j,n){
            cout<<result[i][j];
            if(j==n-1) cout<<endl;
            else cout<<" ";
        }
    }
}