AtCoder 過去問精選10問 をGo言語で解いてみた

## はじめに

何番煎じかわかりませんが、Go言語の基本文法を練習するのによさそうだったので、[AtCoder に登録したら次にやること ～ これだけ解けば十分闘える！過去問精選 10 問 ～](https://qiita.com/drken/items/fd4e5e3630d0f5859067)を解いてみました。
普段はPythonしか書いていないので、書き方の違いにも軽く触れてみました。

実際に解いてみる場合は、[AtCoder Beginners Selection](https://atcoder.jp/contests/abs)に問題がまとめられているので便利です。

## 問題

### [0. PracticeA - Welcome to AtCoder](https://atcoder.jp/contests/abs/tasks/practice_1)

標準入出力の練習です。
標準出力は特に問題ないですが、標準入力については、先に変数を宣言してから、ポインタを使って値を更新するような形になります。

ひとまずは、`fmt.Scan()`と`fmt.Println()`が使えれば問題ないと思います。
`fmt.Scan()`は標準入力をスペースだけでなく改行でも区切るので注意しましょう。

#### 解答例

```go
package main

import "fmt"

func main() {
	var (
		a, b, c int
		s       string
	)
	fmt.Scan(&a, &b, &c, &s)

	fmt.Println(a+b+c, s)
}
```

### [1. ABC086A - Product](https://atcoder.jp/contests/abs/tasks/abc086_a)

条件分岐（if文）の練習です。
また、$a \times b$ が偶数になるのは、$a, b$ のいずれかが偶数のときです。

#### 解答例

```go
package main
 
import "fmt"
 
func main() {
	var a, b int
	fmt.Scan(&a, &b)

	if a%2 == 0 || b%2 == 0 {
		fmt.Println("Even")
	} else {
		fmt.Println("Odd")
	}
}
```

### [2. ABC081A - Placing Marbles](https://atcoder.jp/contests/abs/tasks/abc081_a)

各桁は0か1しか取らないので、それぞれが1と等しいか比較すればよいです。
比較回数が3回で固定のため、for文を使わなくても十分いけます。

ただし、Go言語では文字列から`string[index]`の形で1文字だけ抜き出すと文字列ではなくバイトになるため、stringの`"1"`ではなくruneの`'1'`でないと正しく比較できないことに注意してください。

#### 解答例

```go
package main

import (
	"fmt"
)

func main() {
	var s string
	fmt.Scan(&s)

	ans := 0
	if s[0] == '1' {
		ans++
	}
	if s[1] == '1' {
		ans++
	}
	if s[2] == '1' {
		ans++
	}

	fmt.Println(ans)
}
```

#### 別解

以下のように`strconv.Atoi()`を使用して数値型に変換する方法もあります。数字が0から9までの値を取る場合などは、条件式を10個書くのは面倒なのでこちらの方がよいでしょう。
ここで、`strconv.Atoi()`は2つ目の戻り値としてエラーを返しますが、今回は使わないので`_`で受けておきましょう。

また、GoではPythonとは違って`int()`で文字列を整数に変換することはできないので注意しましょう。

```go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    var s string
    fmt.Scan(&s)

	ans := 0
	tmp, _ := strconv.Atoi(string(s[0]))
	ans += tmp
	tmp, _ = strconv.Atoi(string(s[1]))
	ans += tmp
	tmp, _ = strconv.Atoi(string(s[2]))
	ans += tmp

    fmt.Println(ans)
}
```

### [3. ABC081B - Shift only](https://atcoder.jp/contests/abs/tasks/abc081_b)

2で割り切れない数字が出てくるまで繰り返し割ります。繰り返し回数がわからないのでfor文の出番です。
Goにはwhile文やfor each文がありませんが、for文で代用することが可能です。

また、ここでは数値の配列を管理する必要があるので、スライスを使用してみます。
スライスは`make()`を使って生成します。

#### 解答例

```go
package main

import (
	"fmt"
)

func main() {
	var n int
	fmt.Scan(&n)
	nums := make([]int, n)
	// Pythonの for i in range(n): に相当
	for i := 0; i < n; i++ {
		fmt.Scan(&nums[i])
	}

	var ans int
	ok := true
	// Pythonの while ok: に相当
	for ok {
		// Pythonの for i, num in enumerate(nums): に相当
		for i, num := range nums {
			if num%2 != 0 {
				ok = false
				break
			}
			nums[i] /= 2
		}
		if ok {
			ans++
		}
	}

	fmt.Println(ans)
}
```

### [4. ABC087B - Coins](https://atcoder.jp/contests/abs/tasks/abc087_b)

多重ループの問題です。
for文を3つネストさせれば簡単に解ける問題ですが、1つ減らして計算量を削減しています。

また、Goでは、if文を書くときに条件式の前に変数を宣言することができます。ここで宣言した変数はスコープがif文の中に制限されるため、条件式の中で使いたいが、if文の外では使わない変数を宣言するのに便利です。

#### 解答例

```go
package main

import (
	"fmt"
)

func main() {
	var a, b, c, x, ans int
	fmt.Scan(&a, &b, &c, &x)

	for i := 0; i <= a; i++ {
		for j := 0; j <= b; j++ {
			if k := (x - 500*i - 100*j) / 50; 0 <= k && k <= c {
				ans++
			}
		}
	}

	fmt.Println(ans)
}
```

### [5. ABC083B - Some Sums](https://atcoder.jp/contests/abs/tasks/abc083_b)

ポイントは10進数の整数の取り扱いです。
「10で割った余りを取り出す」→「10で割る」を繰り返せば各桁の値を取り出すことができます。

今回は、この処理を`sumOfDigits()`関数として定義しました。
Goでは、戻り値の型を指定するときに、変数名も併せて定義しておくことができます。この場合、関数内での変数宣言を省略できます。また、returnの対象を明示しない場合、定義しておいた変数をreturnします。

#### 解答例

```go
package main
 
import (
	"fmt"
)
 
func sumOfDigits(n int) (r int) {
	for n > 0 {
		r += n % 10
		n /= 10
	}
	return
}
 
func main() {
	var n, a, b, ans int
	fmt.Scan(&n, &a, &b)
 
	for i := 1; i <= n; i++ {
		if s := sumOfDigits(i); a <= s && s <= b {
			ans += i
		}
	}
 
	fmt.Println(ans)
}
```

### [6. ABC088B - Card Game for Two](https://atcoder.jp/contests/abs/tasks/abc088_b)

ソートの問題です。
Goでは、ソートには`sort`パッケージを使用します。
int型のスライスをソートする場合、`sort.Ints(slice)`または`sort.Sort(sort.IntSlice(n))`とします。
降順にソートしたい場合は、`sort.Sort(sort.Reverse(sort.IntSlice(n)))`とします。

降順ソートさえできてしまえば、後は貪欲法でAliceとBobに交互に得点を割り振ればOKです。

#### 解答例

```go
package main

import (
	"fmt"
	"sort"
)

func main() {
	var n int
	fmt.Scan(&n)

	nums := make([]int, n)
	for i := range nums {
		fmt.Scan(&nums[i])
	}

	var alice, bob int
	sort.Sort(sort.Reverse(sort.IntSlice(nums)))
	for i, num := range nums {
		if i%2 == 0 {
			alice += num
		} else {
			bob += num
		}
	}
	fmt.Println(alice - bob)
}
```

### [7. ABC085B - Kagami Mochi](https://atcoder.jp/contests/abs/tasks/abc085_b)

与えられた餅の直径のユニークな値を出せばよいです。

Pythonであればset型を使えば一発でしたが、Goには集合を表す型はないのでmap型で代用します。
Goのmap型は、スライスと同様にmakeで生成します。

#### 解答例

```go
package main

import (
	"fmt"
)

func main() {
	var n int
	fmt.Scan(&n)
	m := make(map[int]bool)
	for i := 0; i < n; i++ {
		var d int
		fmt.Scan(&d)
		m[d] = true
	}

	fmt.Println(len(m))
}
```

### [8. ABC085C - Otoshidama](https://atcoder.jp/contests/abs/tasks/abc085_c)

4\. と似た問題ですが、3重ループにすると時間が厳しいので2重ループで解きます。
文法的には特筆すべき点はありません。

#### 解答例

```go
package main

import "fmt"

func main() {
	var n, y int
	fmt.Scan(&n, &y)

	for i := 0; i <= n; i++ {
		if i*10000 > y {
			break
		}
		for j := 0; j <= n-i; j++ {
			k := n - i - j
			if i*10000+j*5000+k*1000 == y {
				fmt.Println(i, j, k)
				return
			}
		}
	}
	fmt.Println(-1, -1, -1)
}
```

### [9. ABC049C - 白昼夢](https://atcoder.jp/contests/abs/tasks/arc065_a)

いろいろやり方はありますが、正規表現でやるのが簡単かと思います。
Go言語では`regexp`パッケージを使います。
※ どうやら、Go言語の正規表現はあまり処理が早くないらしいです。

#### 解答例

```go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	var s string
	fmt.Scan(&s)

	r := regexp.MustCompile(`^(dream(er)?|erase(r)?)*$`)
	if r.MatchString(s) {
		fmt.Println("YES")
	} else {
		fmt.Println("NO")
	}
}
```

### [10. ABC086C - Traveling](https://atcoder.jp/contests/abs/tasks/arc089_a)

$t_0 = x_0 = y_0 = 0, ~ dt_i = dt_{i+1} - dt_i, ~ dx_i = |x_{i+1} - x_i|, ~ dy_i = |y_{i+1} - y_i|$ とすると、
旅行プランを実行できるのは、任意の $i ~ (0 \leq i < N)$ に対して、以下の2条件をみたすときです。

* $dx_i + dy_i \leq dt_i$
* $dx_i + dy_i \equiv dt_i \pmod 2$

絶対値の計算は`math.Abs()`がありますが、int型の引数で使いたいので今回は自作しました。

#### 解答例

```go
package main

import (
	"fmt"
)

func abs(n int) int {
	if n < 0 {
		return -n
	}
	return n
}

func main() {
	var n int
	fmt.Scan(&n)
	t := make([]int, n+1)
	x := make([]int, n+1)
	y := make([]int, n+1)
	for i := 1; i <= n; i++ {
		fmt.Scan(&t[i], &x[i], &y[i])
	}

	ans := "Yes"
	for i := 0; i < n; i++ {
		dt := t[i+1] - t[i]
		dxy := abs(x[i+1]-x[i]) + abs(y[i+1]-y[i])
		if dt < dxy || dt%2 != dxy%2 {
			ans = "No"
			break
		}
	}

    fmt.Println(ans)
}
```
## 参考

* [AtCoder に登録したら次にやること ～ これだけ解けば十分闘える！過去問精選 10 問 ～](https://qiita.com/drken/items/fd4e5e3630d0f5859067)
    * 元ネタです。
* [AtCoder Beginners Selection](https://atcoder.jp/contests/abs)
    * 今回の問題がコンテスト形式でまとまっているので便利。
* [A Tour of Go](https://go-tour-jp.appspot.com/)
    * Goの基本文法の学習にどうぞ。

## まとめ

普段はずっとPythonで書いているので、困惑する部分もありましたがよい勉強になりました。
`defer`や`panic`, `go`など、競技プログラミングでは使わなそうな機能については別で勉強したいと思います。