はじめに
AtCoder Beginner Contest 271の復習記事です。
記事におけるScanner
クラスは、自作の入力クラスです。
コンテスト
https://atcoder.jp/contests/abc271
問題A
C#では、整数を文字列に変換するときに、書式指定子X
を指定することで16進数文字列に変換することができ、書式指定子の後に数字を加えることで桁数を指定できます。
16 進書式指定子 (X)
public static void Solve()
{
var N = Scanner.Scan<int>();
Console.WriteLine(N.ToString("X2"));
}
問題B
全ての配列を、配列の配列として保持しておき、s
番目の配列の、t
番目の値を出力します。
public static void Solve()
{
var (N, Q) = Scanner.Scan<int, int>();
var A = new int[N][];
for (var i = 0; i < N; i++)
{
var line = Scanner.ScanEnumerable<int>().ToArray();
A[i] = line[1..];
}
while (Q-- > 0)
{
var (s, t) = Scanner.Scan<int, int>();
s--;
t--;
Console.WriteLine(A[s][t]);
}
}
問題C
x
巻まで読むことができるかという二部探索を行います。
判定式としては、持っている単行本のうち重複を除いたx
以下の巻数をc1
、x
より大きい巻数や重複した2冊目以降の数をc2 (c2==N-c1)
としたとき、足りない数の2倍がc2
以下であるか(x-c1)*2<=c2
を判定します。
public static void Solve()
{
var N = Scanner.Scan<int>();
var A = Scanner.ScanEnumerable<int>().Distinct().ToArray();
bool F(int x)
{
var c1 = A.Count(v => v <= x);
var c2 = N - c1;
return (x - c1) * 2 <= c2;
}
var answer = BinarySearch((int)1e9, 0, F);
Console.WriteLine(answer);
}
public static int BinarySearch(int ng, int ok, Func<int, bool> func)
{
while (Math.Abs(ok - ng) > 1)
{
var m = (ok + ng) / 2;
if (func(m)) ok = m;
else ng = m;
}
return ok;
}
問題D
dp[i,j,k]:=i番目までのカードみたとき状態がk(表裏)のときにの合計がjが成り立つか
という動的計画法をとき、dp[N,S,0]
かdp[N,S,1]
が成り立つかを判定し、成り立つときは経路復元を行います。
経路復元では、dp[i,j,k]
が成り立つときのi-1
番目のjとk
をメモしておきます。
public static void Solve()
{
var (N, S) = Scanner.Scan<int, int>();
var dp = new bool[N + 1, S + 1, 2];
var prev = new (int, int)[N + 1, S + 1, 2];
dp[0, 0, 0] = dp[0, 0, 1] = true;
for (var i = 0; i < N; i++)
{
var (a, b) = Scanner.Scan<int, int>();
for (var j = 0; j <= S; j++)
{
if (j + a <= S)
{
for (var k = 0; k < 2; k++)
{
if (dp[i, j, k])
{
dp[i + 1, j + a, 0] |= dp[i, j, k];
prev[i + 1, j + a, 0] = (j, k);
}
}
}
if (j + b <= S)
{
for (var k = 0; k < 2; k++)
{
if (dp[i, j, k])
{
dp[i + 1, j + b, 1] |= dp[i, j, k];
prev[i + 1, j + b, 1] = (j, k);
}
}
}
}
}
if (dp[N, S, 0] || dp[N, S, 1])
{
var list = new List<int>();
var curr = dp[N, S, 0] ? (S, 0) : (S, 1);
for (var i = N; i > 0; i--)
{
list.Add(curr.Item2);
curr = prev[i, curr.Item1, curr.Item2];
}
list.Reverse();
var answer = string.Join("", list.Select(x => x == 0 ? 'H' : 'T'));
Console.WriteLine("Yes");
Console.WriteLine(answer);
}
else
{
Console.WriteLine("No");
}
}
問題E
dp[i][j]:=Eのi番目までの道を使った時の都市jへの最小コスト
とした動的計画法をときます。
良い経路はE
の部分列であり、E[1]->E[2]
やE[1]->E[3]
のようなことも可能であるため、i
ごとにコストを保持する必要はなく、一次元のみで求めることができます。
public static void Solve()
{
var (N, M, K) = Scanner.Scan<int, int, int>();
var G = new List<(int, long)>[N].Select(x => new List<(int, long)>()).ToArray();
const long inf = (long)1e18;
var Edges = new (int U, int V, long C)[M];
for (var i = 0; i < M; i++)
{
var (a, b, c) = Scanner.Scan<int, int, long>();
a--; b--;
G[a].Add((b, c));
G[b].Add((a, c));
Edges[i] = (a, b, c);
}
var E = Scanner.ScanEnumerable<int>().Select(x => x - 1).ToArray();
var dp = new long[N];
Array.Fill(dp, inf);
dp[0] = 0;
for (var i = 0; i < K; i++)
{
var (a, b, c) = Edges[E[i]];
if (dp[a] != inf)
{
dp[b] = Math.Min(dp[b], dp[a] + c);
set.Add(b);
}
}
var answer = dp[N - 1];
if (answer == inf) answer = -1;
Console.WriteLine(answer);
}