はじめに
AtCoder Beginner Contest 260の復習記事です。
記事におけるScanner
クラスは、自作の入力クラスです。
コンテスト
https://atcoder.jp/contests/abc260
問題A
文字ごとの出現数を数え上げ、出現数が1つのみ文字を出力します。
C#の場合、char
型を数値として扱い、小文字のアルファベットからa
を引くことでa,b,c,...
を0,1,2,...
として管理することができます。
public static void Solve()
{
var S = Scanner.Scan<string>();
var count = new int[26];
foreach (var c in S)
{
count[c - 'a']++;
}
for (var i = 0; i < 26; i++)
{
if (count[i] == 1)
{
Console.WriteLine((char)('a' + i));
return;
}
}
Console.WriteLine(-1);
}
問題B
受験生の番目、数学のスコア、英語のスコアをTuple
などのデータクラスとして管理します。
i
番目の学生が合格したかをフラグとして管理し、それぞれの条件ごとにソートして合格となっていない受験生が対象となるかを判定することで、時間計算量O(NlogN+N)
で答えを求めることができます。
C#の場合、Array.Sort メソッドにComparison<T>
を指定することで、比較関数を与えてソートすることができます。
public static void Solve()
{
var (N, X, Y, Z) = Scanner.Scan<int, int, int, int>();
var A = Scanner.ScanEnumerable<int>().ToArray();
var B = Scanner.ScanEnumerable<int>().ToArray();
var accepts = new bool[N];
var C = A.Zip(B).Select((x, i) => (A: x.First, B: x.Second, ID: i)).ToArray();
Array.Sort(C, (x, y) =>
{
var result = y.A.CompareTo(x.A);
return result == 0 ? x.ID.CompareTo(y.ID) : result;
});
for (var i = 0; i < N && X > 0; i++)
{
if (accepts[C[i].ID]) continue;
accepts[C[i].ID] = true;
X--;
}
Array.Sort(C, (x, y) =>
{
var result = y.B.CompareTo(x.B);
return result == 0 ? x.ID.CompareTo(y.ID) : result;
});
for (var i = 0; i < N && Y > 0; i++)
{
if (accepts[C[i].ID]) continue;
accepts[C[i].ID] = true;
Y--;
}
Array.Sort(C, (x, y) =>
{
var result = (y.A + y.B).CompareTo(x.A + x.B);
return result == 0 ? x.ID.CompareTo(y.ID) : result;
});
for (var i = 0; i < N && Z > 0; i++)
{
if (accepts[C[i].ID]) continue;
accepts[C[i].ID] = true;
Z--;
}
for (var i = 0; i < N; i++)
{
if (accepts[i])
{
Console.WriteLine(i + 1);
}
}
}
問題C
動的計画法で答えを求めます。
赤い宝石と青い宝石のレベルごとの個数をそれぞれ管理し、レベルN
からレベル2
まで計算します。
遷移としては、レベルごとに先に赤い宝石を変換し、次に青い宝石を変換します。
public static void Solve()
{
var (N, X, Y) = Scanner.Scan<int, long, long>();
var Red = new long[N + 1];
var Blue = new long[N + 1];
Red[N] = 1;
for (var i = N; i >= 2; i--)
{
Red[i - 1] += Red[i];
Blue[i] += Red[i] * X;
Red[i - 1] += Blue[i];
Blue[i - 1] += Blue[i] * Y;
}
var answer = Blue[1];
Console.WriteLine(answer);
}
問題D
表になっているカードの集合のうちX
以上の値を高速に求められるようなデータ構造と、カードの山を管理するデータ構造を使って何ターン目に食べられるかを判定します。
前者のデータ構造では、X
以上の値を時間計算量O(N)
で探索してしまうと、全体の時間計算量がO(N^2)
となってしまい実行時間制限に間に合いません。
そのため、二部探索のような時間計算量O(logN)
で探索できるようなデータ構造(C++の場合はset
、C#の場合はSortedSet
)が必要です。
SortedSet
からGetViewBetween
したサブセットのCount
プロパティが、場合によっては時間計算量O(1)
ではなさそうなので、LINQ
のAny
で存在判定をしたほうが良さそうです。
public static void Solve()
{
var (N, K) = Scanner.Scan<int, int>();
var P = Scanner.ScanEnumerable<int>().ToArray();
var answer = new int[N + 1];
Array.Fill(answer, -1);
var set = new SortedSet<int>();
var dict = new Dictionary<int, List<int>>();
var root = new int[N + 1];
for (var i = 0; i < N; i++)
{
var p = P[i];
var r = root[p] = p;
var subset = set.GetViewBetween(p, N);
if (subset.Any())
{
var q = subset.Min;
r = root[p] = root[q];
set.Remove(q);
}
else
{
dict[r] = new List<int>();
}
set.Add(p);
dict[r].Add(p);
if (dict[r].Count >= K)
{
foreach (var v in dict[r])
{
answer[v] = i + 1;
}
set.Remove(p);
dict.Remove(r);
}
}
Console.WriteLine(string.Join("\n", answer.Skip(1)));
}