はじめに
AtCoder Beginner Contest 309の復習記事です。
記事におけるScanner
クラスは、自作の入力クラスです。
Scannerクラス
public static class Scanner
{
public static T Scan<T>() where T : IConvertible => Convert<T>(ScanStringArray()[0]);
public static (T1, T2) Scan<T1, T2>() where T1 : IConvertible where T2 : IConvertible
{
var input = ScanStringArray();
return (Convert<T1>(input[0]), Convert<T2>(input[1]));
}
public static (T1, T2, T3) Scan<T1, T2, T3>() where T1 : IConvertible where T2 : IConvertible where T3 : IConvertible
{
var input = ScanStringArray();
return (Convert<T1>(input[0]), Convert<T2>(input[1]), Convert<T3>(input[2]));
}
public static (T1, T2, T3, T4) Scan<T1, T2, T3, T4>() where T1 : IConvertible where T2 : IConvertible where T3 : IConvertible where T4 : IConvertible
{
var input = ScanStringArray();
return (Convert<T1>(input[0]), Convert<T2>(input[1]), Convert<T3>(input[2]), Convert<T4>(input[3]));
}
public static (T1, T2, T3, T4, T5) Scan<T1, T2, T3, T4, T5>() where T1 : IConvertible where T2 : IConvertible where T3 : IConvertible where T4 : IConvertible where T5 : IConvertible
{
var input = ScanStringArray();
return (Convert<T1>(input[0]), Convert<T2>(input[1]), Convert<T3>(input[2]), Convert<T4>(input[3]), Convert<T5>(input[4]));
}
public static (T1, T2, T3, T4, T5, T6) Scan<T1, T2, T3, T4, T5, T6>() where T1 : IConvertible where T2 : IConvertible where T3 : IConvertible where T4 : IConvertible where T5 : IConvertible where T6 : IConvertible
{
var input = ScanStringArray();
return (Convert<T1>(input[0]), Convert<T2>(input[1]), Convert<T3>(input[2]), Convert<T4>(input[3]), Convert<T5>(input[4]), Convert<T6>(input[5]));
}
public static IEnumerable<T> ScanEnumerable<T>() where T : IConvertible => ScanStringArray().Select(Convert<T>);
private static string[] ScanStringArray()
{
var line = Console.ReadLine()?.Trim() ?? string.Empty;
return string.IsNullOrEmpty(line) ? Array.Empty<string>() : line.Split(' ');
}
private static T Convert<T>(string value) where T : IConvertible => (T)System.Convert.ChangeType(value, typeof(T));
}
コンテスト
https://atcoder.jp/contests/abc309
問題A
A<B
であることから、A+1==B
であることが条件の一つとなります。
また、A
が3
、6
、9
の場合、右に隣接するB
は存在しないため、A
が3の倍数ではないときが、もう一つの条件となります。
public static void Solve()
{
var (A, B) = Scanner.Scan<int, int>();
var answer = A + 1 == B && (A % 3 != 0);
Console.WriteLine(answer ? "Yes" : "No");
}
問題B
時計回りに1
行目、N
列目、N
行目、1
列目を更新していくことを考えます。
ある場所を更新するとき、その直前に更新した値がその場所に移動し、更新された値が次の場所に移動することがわかります。
このことから、直前に更新した値を保持しながら外側のマスを順番に更新していくことで、答えを得ることができます。
public static void Solve()
{
var N = Scanner.Scan<int>();
var A = new int[N][];
for (var i = 0; i < N; i++)
{
A[i] = Scanner.Scan<string>().Select(x => x - '0').ToArray();
}
var tmp1 = A[1][0];
for (var i = 0; i < N; i++)
{
var tmp2 = A[0][i];
A[0][i] = tmp1;
tmp1 = tmp2;
}
for (var i = 1; i < N; i++)
{
var tmp2 = A[i][N - 1];
A[i][N - 1] = tmp1;
tmp1 = tmp2;
}
for (var i = N - 2; i >= 0; i--)
{
var tmp2 = A[N - 1][i];
A[N - 1][i] = tmp1;
tmp1 = tmp2;
}
for (var i = N - 2; i >= 0; i--)
{
var tmp2 = A[i][0];
A[i][0] = tmp1;
tmp1 = tmp2;
}
Printer.Print2D(A);
}
問題C
初日に飲む必要がある薬はb
の総和となり、各a+1
日目に飲む分はb
錠減ることがわかります。
このことから、各a+1
日目に何錠減るかをまとめ、薬が減る日が早い順に飲む必要がある薬を減らしていき、K
以下になった日が答えとなります。
public static void Solve()
{
var (N, K) = Scanner.Scan<int, long>();
var dict = new Dictionary<long, long>();
dict[1] = 0;
long cum = 0;
for (var i = 0; i < N; i++)
{
var (a, b) = Scanner.Scan<long, long>();
cum += b;
if (!dict.ContainsKey(a + 1)) dict[a + 1] = 0;
dict[a + 1] += b;
}
foreach (var (a, b) in dict.OrderBy(x => x.Key))
{
cum -= b;
if (cum <= K)
{
Console.WriteLine(a);
return;
}
}
}
問題D
頂点1
から頂点N1+N2
への最長となる経路は、頂点1
から最も遠い頂点と頂点N1+N2
から最も遠い頂点間に辺を結ぶことで達成することができます。
このことから、N1
個の頂点からなるグラフG1
において頂点1
から距離と、N2
個の頂点からなるグラフG2
において頂点N1+N2
からの距離をそれぞれ幅優先探索などを行い求め、各距離の最大値に辺を一つ追加したものが答えとなります。
public static void Solve()
{
var (N1, N2, M) = Scanner.Scan<int, int, int>();
var G1 = new List<int>[N1].Select(x => new List<int>()).ToArray();
var G2 = new List<int>[N2].Select(x => new List<int>()).ToArray();
for (var i = 0; i < M; i++)
{
var (a, b) = Scanner.Scan<int, int>();
a--; b--;
if (a < N1)
{
G1[a].Add(b);
G1[b].Add(a);
}
else
{
a -= N1;
b -= N1;
G2[a].Add(b);
G2[b].Add(a);
}
}
int MaxDist(List<int>[] G, int N, int s)
{
var dist = new int[N];
Array.Fill(dist, -1);
var queue = new Queue<int>();
queue.Enqueue(s);
dist[s] = 0;
while (queue.Count > 0)
{
var u = queue.Dequeue();
foreach (var v in G[u])
{
if (dist[v] != -1) continue;
dist[v] = dist[u] + 1;
queue.Enqueue(v);
}
}
return dist.Max();
}
var answer = MaxDist(G1, N1, 0) + MaxDist(G2, N2, N2 - 1) + 1;
Console.WriteLine(answer);
}
問題E
各親u
から子v
への有向辺を張ったグラフをG
とします。
人i
からみた保証対象となる代の最大値をdp[i]
、親をu
、子をv
としたとき、dp[v]
の最大値はMax(dp[v],dp[u]-1)
になります。
P[i]<=i-1
であることから、親を順に走査して子に最大値を伝播してくことで各dp[i]
の値を求めることができ、dp[i]>=0
となる人の数が答えとなります。
public static void Solve()
{
var (N, M) = Scanner.Scan<int, int>();
var G = new List<int>[N].Select(x => new List<int>()).ToArray();
var P = Scanner.ScanEnumerable<int>().ToArray();
for (var i = 0; i < P.Length; i++)
{
var u = P[i] - 1;
var v = i + 1;
G[u].Add(v);
}
var dp = new int[N];
Array.Fill(dp, -1);
for (var i = 0; i < M; i++)
{
var (x, y) = Scanner.Scan<int, int>();
x--;
dp[x] = Math.Max(dp[x], y);
}
var answer = 0;
for (var u = 0; u < N; u++)
{
foreach (var v in G[u])
{
dp[v] = Math.Max(dp[v], dp[u] - 1);
}
if (dp[u] >= 0) answer++;
}
Console.WriteLine(answer);
}