はじめに
AtCoder Beginner Contest 240の復習記事です。
記事におけるScanner
クラスは、自作の入力クラスです。
コンテスト
https://atcoder.jp/contests/abc240
問題A
10で割ったときのあまりの値を比較することで、ループを再現することができます。
また、隣り合っていることは差が1であればいいので、それを確かめます。
例えば、a=2, b=3
のときは、2+1==3
となり、a=1,b=10
の時は1==0+1
となります。
public static void Solve()
{
var (A, B) = Scanner.Scan<int, int>();
var answer = (A + 1) % 10 == B % 10 || (B + 1) % 10 == A % 10;
Console.WriteLine(answer ? "Yes" : "No");
}
問題B
重複を削除したときの個数が答えとなります。
C#のLINQには、Distinct
というシーケンス内の重複を除いたシーケンスを返すメソッドがあるため、それを使い個数を数えます。
public static void Solve()
{
var N = Scanner.Scan<int>();
var A = Scanner.ScanEnumerable<long>().ToArray();
var answer = A.Distinct().Count();
Console.WriteLine(answer);
}
問題C
動的計画法で答えを求めます。
i
番目のジャンプを座標j(0<=j<=X)
から行うと、i+1
番目では、j+a
またはj+b
に存在することができます。初期状態の0
をtrue
としたとき、そこからの遷移を計算し、N
回ジャンプ後のX
の値がtrue
ならば存在することができると表現できます。
public static void Solve()
{
var (N, X) = Scanner.Scan<int, int>();
var dp = new bool[N + 1, X + 1];
dp[0, 0] = true;
for (var i = 0; i < N; i++)
{
var (a, b) = Scanner.Scan<int, int>();
for (var j = 0; j <= X; j++)
{
if (j + a <= X) dp[i + 1, j + a] |= dp[i, j];
if (j + b <= X) dp[i + 1, j + b] |= dp[i, j];
}
}
var answer = dp[N, X];
Console.WriteLine(answer ? "Yes" : "No");
}
問題D
スタックに順番にボールを追加していき、同じ数字がK個連続した場合は削除する操作を行います。このとき一つの数字につき1つの値を入れていると、計算量はO(N^2)
になってしまうので、連続した値は値と個数の一つのオブジェクトとしてまとめ、スタックに入っているボールの個数を管理することで、計算量をO(N)
に抑えることができます。
具体的には、もしスタックが空またはスタックのトップと異なる値の場合は、値と個数1をタプルとしてまとめてスタックに追加し、もしスタックのトップと同じ値の場合は、スタックのトップの個数を更新します。その後、スタックのトップを順にみていき、値と個数が同じ場合はスタックから取り除き、現在の個数も減少させます。
public static void Solve()
{
var N = Scanner.Scan<int>();
var A = Scanner.ScanEnumerable<int>().ToArray();
var curr = 0;
var stack = new Stack<(int V, int C)>();
foreach (var a in A)
{
curr++;
if (stack.Count == 0)
{
stack.Push((a, 1));
Console.WriteLine(curr);
continue;
}
var (v, c) = stack.Pop();
if (a == v)
{
c++;
stack.Push((v, c));
}
else
{
stack.Push((v, c));
stack.Push((a, 1));
}
while (stack.Count > 0)
{
(v, c) = stack.Peek();
if (v == c)
{
stack.Pop();
curr -= c;
}
else
{
break;
}
}
Console.WriteLine(curr);
}
}
問題E
問題文の根付き木について、子を持たない頂点に対してそれぞれ異なる値を与えたときに、部分木i
が持つ値群の最小値と最大値をそれぞれLi
とRi
としたとき、頂点i
は区間[Li, Ri]
を持つと解釈することができます。
そのため、深さ優先探索を行い、子を持たない頂点の場合はそれまでに出現していない値をLi
とRi
に設定し、子を持つ頂点は、子のLi
とRi
の中でそれぞれ最小、最大となるものを選択することで、答えとなる区間を求めることができます。
public static void Solve()
{
var N = Scanner.Scan<int>();
var G = new List<int>[N].Select(x => new List<int>()).ToArray();
for (var i = 0; i < N - 1; i++)
{
var (u, v) = Scanner.Scan<int, int>();
u--; v--;
G[u].Add(v);
G[v].Add(u);
}
const int inf = (int)1e9;
var L = new int[N];
var R = new int[N];
var curr = 1;
(int L, int R) Dfs(int u, int p)
{
var l = inf;
var r = -inf;
foreach (var v in G[u])
{
if (v == p) continue;
var (ll, rr) = Dfs(v, u);
l = Math.Min(l, ll);
r = Math.Max(r, rr);
}
if (l == inf)
{
L[u] = curr;
R[u] = curr;
curr++;
}
else
{
L[u] = l;
R[u] = r;
}
return (L[u], R[u]);
}
Dfs(0, -1);
foreach (var (l, r) in L.Zip(R))
{
Console.WriteLine($"{l} {r}");
}
}