はじめに
AtCoder Beginner Contest 268の復習記事です。
記事におけるScanner
クラスは、自作の入力クラスです。
コンテスト
https://atcoder.jp/contests/abc268
問題A
0
から100
までの長さ101
のbool
配列を用意して、与えられた整数の位置をtrue
にし、そのtrue
の数を数え上げます。
public static void Solve()
{
var A = Scanner.ScanEnumerable<int>().ToArray();
var exists = new bool[101];
for (var i = 0; i < 5; i++)
{
exists[A[i]] = true;
}
var answer = exists.Count(x => x);
Console.WriteLine(answer);
}
問題B
Sの長さ>Tの長さ
の場合はfalse
であり、それ以外の時にS
の長さの範囲で文字がすべて一致するかを判断します。
C#では、String.StartWith
メソッドで引数に与えられた文字列で文字列が始まっているかを判断できます。
public static void Solve()
{
var S = Scanner.Scan<string>();
var T = Scanner.Scan<string>();
var answer = T.StartsWith(S);
Console.WriteLine(answer ? "Yes" : "No");
}
問題C
始点を全探索して全ての値がi-1
、i
、i+1
のいずれかの目の前にあるかを探索してしまうと、時間計算量がO(N^2)
となり実行時間制限に間に合いません。
そこで、人i
と料理P[i]
の距離(P[i]-i)%N
の個数に変換することで、距離d-1
、d
、d+1
にある個数の和の最大値を求める問題に変換することで、時間計算量O(N)
で答えを求めることができます。
public static void Solve()
{
var N = Scanner.Scan<int>();
var P = Scanner.ScanEnumerable<int>().ToArray();
var count = new int[N];
for (var i = 0; i < N; i++)
{
count[(P[i] - i + N) % N]++;
}
var answer = 0;
for (var i = 0; i < N; i++)
{
var c = 0;
for (var j = 0; j < 3; j++)
{
c += count[(i + j) % N];
}
answer = Math.Max(answer, c);
}
Console.WriteLine(answer);
}
問題D
N
個の文字列の順列として並べ替えたものの間に1以上の任意の数の_
を追加した文字列を全て列挙し、M
個の文字列に存在しない文字列があるかどうかを判定します。
_
を追加する文字列を生成する方法として、現在の_
の個数、現在_
を追加しようとしてる位置、残りの追加できる_
の数を管理しながら深さ優先探索を行うことができます。
また、N=1
の時がコーナーケースで、3
文字以上16
文字以下であることの確認が必要です。
public static void Solve()
{
var (N, M) = Scanner.Scan<int, int>();
var S = new string[N];
var remains = 16 - (N - 1);
for (var i = 0; i < N; i++)
{
S[i] = Scanner.Scan<string>();
remains -= S[i].Length;
}
var T = new HashSet<string>();
for (var i = 0; i < M; i++)
{
var s = Scanner.Scan<string>();
T.Add(s);
}
if (N == 1)
{
Console.WriteLine(!T.Contains(S[0]) && 3 <= S[0].Length && S[0].Length <= 16 ? S[0] : "-1");
return;
}
var set = new HashSet<string>();
var count = new int[N - 1];
Array.Fill(count, 1);
foreach (var perm in Enumerable.Range(0, N).Permute(N))
{
void Dfs(int curr, int rem)
{
if (rem < 0) return;
if (curr >= N - 1)
{
var builder = new StringBuilder();
for (var i = 0; i < N; i++)
{
builder.Append(S[perm[i]]);
if (i < N - 1) builder.Append('_', count[i]);
}
var x = builder.ToString();
set.Add(x);
return;
}
for (var k = 0; k <= rem; k++)
{
count[curr] += k;
Dfs(curr + 1, rem - k);
count[curr] -= k;
}
}
Dfs(0, remains);
}
foreach (var x in set)
{
if (!T.Contains(x))
{
Console.WriteLine(x);
return;
}
}
Console.WriteLine("-1");
}
以下のような順列を列挙するメソッドを使用しました。
public static partial class EnumerableExtension
{
public static IEnumerable<TSource[]> Permute<TSource>(this IEnumerable<TSource> source, int count)
{
if (source is null) throw new ArgumentNullException(nameof(source));
IEnumerable<TSource[]> Inner()
{
var items = source.ToArray();
if (count <= 0 || items.Length < count) throw new ArgumentOutOfRangeException(nameof(count));
var n = items.Length;
var indices = new int[n];
for (var i = 0; i < indices.Length; i++)
{
indices[i] = i;
}
var cycles = new int[count];
for (var i = 0; i < cycles.Length; i++)
{
cycles[i] = n - i;
}
TSource[] Result()
{
var result = new TSource[count];
for (var i = 0; i < count; i++)
{
result[i] = items[indices[i]];
}
return result;
}
yield return Result();
while (true)
{
var done = true;
for (var i = count - 1; i >= 0; i--)
{
cycles[i]--;
if (cycles[i] == 0)
{
for (var j = i; j + 1 < indices.Length; j++)
{
(indices[j], indices[j + 1]) = (indices[j + 1], indices[j]);
}
cycles[i] = n - i;
}
else
{
(indices[i], indices[^cycles[i]]) = (indices[^cycles[i]], indices[i]);
yield return Result();
done = false;
break;
}
}
if (done) yield break;
}
}
return Inner();
}
}