paizaオンラインハッカソン Vol.5に参加してみた

注意: この記事は1年以上前に掲載されたものです。情報が古い場合がありますのでお気を付け下さい。

せっかくなので、paizaオンラインハッカソン Vol.5に参加してみた。どう見ても某ラノベをパクったタイトルなのはさておき、今回はC++で書いてみた。私が実際に書いたコードと簡単な解説を以下に記述してみたい。

(注: 今回の結果についてはsayo22名義で行っています。)

ミッション1

サンプルコード

[cpp]#include <iostream>
using namespace std;

int main(void) {
string str;
getline(cin, str);

string decrypted = "";

for (int idx = 0; idx < str.length(); idx++) {
if (idx % 2 == 0) {
decrypted += str.at(idx);
}
}

cout << decrypted << endl;
return 0;
}[/cpp]

解説

これは入力された文字列をもとに特定の番目(この場合は奇数の番目)の文字を抜き出した文字列を出力するプログラムである。なお、CやC++では基本的に配列や文字列のインデックスは0始まりであるため、一般的なイメージ、つまり1始まりの場合の奇数の番目の文字というのは、CやC++では偶数となる。つまり、この場合は2で割り切れる文字列を抜き取れば問題ない。

C++ではstd::string型にatメソッドがあるので、それを利用すれば簡単に文字を抜き出せるほか、+=演算子で文字を結合できるので、苦労することはないだろう。

結果

cf. ミッション1結果

すべてのテストケースで0.01秒だった。

ミッション2

サンプルコード

[cpp]#include <iostream>
using namespace std;
int main(void){
// 曜日の数値
static const int WEEK = 7;

// 数値の配列を初期化する
int nums[WEEK];
for (auto &num : nums) {
num = 0;
}

// 行数
int line = 0;
while (true) {
// 文字列を取得する
string str;
getline(cin, str);

// 何もなければループを抜ける
if (str.length() == 0) {
break;
}

// 数値にする
int num = stoi(str);
if (line == 0) {
// 0行目
if (num < 1 || 210 < num || num % 7 != 0) {
// 1 <= n <= 210かつ7の倍数という条件を仮に満たしていないときは
// 例外を発する
throw("Invalid number!!");
}
}else{
// 曜日ごとに振り分ける
int day = (line – 1) % WEEK;
nums[day] += num;
}
// ライン番号を追加する
line++;
}

// 出力する
for (auto num : nums) {
cout << num << endl;
}

return 0;
}[/cpp]

解説

これは最初の行目で何日間のデータを取ったのかを判別し、2行目以降は日ごとに数値が入力されるのを、週ごとの合計値を吐き出すためのプログラムである。

C++の場合は、基本的に最初の行については無視しても構わないものと考えるが、設問としては1以上210以下でなおかつ7で割り切れるというのを条件があったので、わざわざそれを満たしていないときに例外を投げるという処理を加えてみた。なお、今回のテストケースでは蛇足となった模様。

2行目以降ではラインごとに曜日が変わるので行番号ごとに7(今回ハードコーディングとなることを防ぐため定数WEEKとした)で割ったあまりの数を振り分けた上でそれぞれに数値を加算した。なお、配列を宣言しただけでは配列内の数値が初期化されないので、宣言した後に必ず初期化しなければならない。今回の場合はすべての要素に0を代入した。

今回はC++11特有の範囲ベースのfor文を使用した。それ以前のC++では使用できないため、要注意である。

結果

cf. ミッション2の結果

これもすべてのテストケースで0.01秒だった。

ミッションRENA

###サンプルコード

[cpp]#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

vector<string> split(const string &str, char sep) {
vector<string> vec;
stringstream sstream(str);
string buf;
while (getline(sstream, buf, sep)) {
vec.push_back(buf);
}
return vec;
}

using namespace std;
int main(void){
string str;
const char BLANK = ‘ ‘;
getline(cin, str);
auto splitted = split(str, BLANK);

// テーブルを作成する
enum {
COLUMNS = 0,
ROWS = 1,
RANGE = 2,
};
const int rows = stoi(splitted[ROWS]);
const int columns = stoi(splitted[COLUMNS]);
const int rangeLines = stoi(splitted[RANGE]);

int table[rows][columns];
for (auto &row : table) {
for (auto &item : row) {
item = 0;
}
}

// テーブルの数値を埋め込む
for (int row = 0; row < rows; row++) {
getline(cin, str);
auto rowItems = split(str, BLANK);
for (int column = 0; column < columns; column++) {
table[row][column] = stoi(rowItems[column]);
}
}

// 範囲を指定する
bool shouldSum[rows][columns];
for (auto &rowItems : shouldSum) {
for (auto &item : rowItems) {
item = false;
}
}

for (int idx = 0; idx < rangeLines; idx++) {
getline(cin, str);
auto rowArr = split(str, BLANK);
enum {
FROM_X,
FROM_Y,
TO_X,
TO_Y,
};

for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
// 表の座標は1始まりのため、0始まりに変換させる
auto trueIdx = [](std::vector<string> arr, int idx) {
return stoi(arr[idx]) – 1;
};

if (trueIdx(rowArr, FROM_X) <= column && column <= trueIdx(rowArr, TO_X)
&& trueIdx(rowArr, FROM_Y) <= row && row <= trueIdx(rowArr, TO_Y)) {
shouldSum[row][column] = true;
}
}
}
}

// 計算する
int total = 0;
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
if (shouldSum[row][column]) {
total += table[row][column];
}
}
}

cout << total << endl;

return 0;
}[/cpp]

注: 一部余計な記述があります。

解説

これは特定のフォーマットに沿った入力からテーブルのサイズと選択する範囲の数を指定した上で、まずは数値を入力し、その後計算対象の範囲を指定するというプログラムである。

私の場合は、以下の手順を踏んで合計値を出した。

  1. テーブルのサイズ(行・桁)と、選択範囲の数を取得する
  2. 1で指定されたサイズのテーブルを作成する。
  3. 2で作成されたテーブルに数値を代入する(1で指定された行の数まで続く)。
  4. テーブルへの数値代入が完了したら、1で指定されたサイズの範囲用のテーブルを作成する(初期化するためにすべての要素をfalseにする)。
  5. 4に左上の座標、右下の座標範囲内のアイテムは計算対象になるため、trueにする(なお、今回はor条件となっているため、一度trueになったらfalseになることはない)。
  6. 5が完了したら、合計値を一旦0で定義した上で、数値テーブルと計算対象判定テーブルのインデックスをなめまわして計算対象であればその数値を加算するという処理を行う。

ミッション1、2と比較するとコード量が長くなるが、問題文を読み、なおかつ何をすればいいのかを冷静に考えれば失敗することはないだろう。

ちなみに、今回はケアレスミスでusing namespace std;が2回も書いてしまったが、これはコンパイラーレベルでも実行レベルでもさほど問題は起きていない上に、減点対象にはなっていない模様。ただ、格好悪いので、実際の開発では行わないように気をつけなければならないだろう。

結果

cf. ミッションRENA結果

割と長いコードで処理も重たい可能性があるにもかかわらず、すべて0.01秒となっていた。C++の実行速度は相当早いことが想定される。

おわりに

ひとまず、RENAルートまで進んでみたが、問題文さえわかれば無理なくコードを書くことはできるだろう。この記事を読んだ方も自分が主に使っている言語でチャレンジしてみると良いだろう。

タイトルとURLをコピーしました