はじめに
AtCoder Beginner Contest 321の復習記事です。
記事における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/abc321
問題A
N
を文字列としてみたとき、全ての1<=i<=N-1
においてN[i]>N[i+1]
が成り立つものが321-like Number
になります。
public static void Solve()
{
var N = Scanner.Scan<string>();
var answer = true;
for (var i = 0; i + 1 < N.Length; i++)
{
answer &= N[i] > N[i + 1];
}
Console.WriteLine(answer ? "Yes" : "No");
}
問題B
最後にk
点取ると固定したときの最終結果がX
以上になるk
を全探索します。
あらかじめ、N-1
試合の合計スコア(sum
)、最低スコア(min
)、最高スコア(max
)を計算しておくと、最終試験でk
点とった場合、最終結果はsum + k - Min(min,k) - Max(max,k)
で求めることができます。
public static void Solve()
{
var (N, X) = Scanner.Scan<int, int>();
var A = Scanner.ScanEnumerable<int>().ToArray();
var min = A.Min();
var max = A.Max();
var sum = A.Sum();
const int Inf = 1 << 30;
var answer = Inf;
for (var k = 0; k <= 100; k++)
{
var mmin = Math.Min(min, k);
var mmax = Math.Max(max, k);
var x = sum + k - mmin - mmax;
if (x >= X)
{
answer = Math.Min(answer, k);
}
}
if (answer == Inf) answer = -1;
Console.WriteLine(answer);
}
問題C
321-like Number
の各桁に同じ数字が現れることはないため、0
から9
の数字をそれぞれ使う/使わないとしたときに、使う数字のみを降順に並べたものが321-like Number
になり得ます。
このことから、それぞれの数字を使う/使わないとする組み合わせ2^10-1
通りをbit全探索し、全ての321-like Number
を列挙してソートすることで、K
番目の値を得ることができます。
public static void Solve()
{
var K = Scanner.Scan<int>();
var S = new List<long>();
for (var s = 1; s < 1 << 10; s++)
{
var list = new List<int>();
for (var i = 0; i < 10; i++)
{
if ((s >> i & 1) == 1) list.Add(i);
}
list.Sort();
list.Reverse();
S.Add(long.Parse(string.Join("", list)));
}
S.Sort();
var answer = S[K];
Console.WriteLine(answer);
}
問題D
ある主菜A[i]
を固定したとき、副菜B
のうち価格がP-A[i]
以上のものは価格がP
となることから、B
のうちP-A[i]
以上のものとP-A[i]
より小さいもので分けて考えることができます。
P-A[i]
以上のものの個数をX
としたとき、P-A[i]
以上のものの価格の総和はM*P
になります。
P-A[i]
より小さいものの個数はM-X
となり、P-A[i]
より小さいものの価格和をS
とすると、P-A[i]
より小さいものの価格の総和はA[i]*(M-X)+S
となります。
B
をあらかじめソートしておき、累積和を求めておくことで、各A[i]
ごとにB
のうちP-A[i]
以上の個数を時間計算量O(logM)
で求めることができ、累積和からP-A[i]
より小さいものの価格和を時間計算量O(1)
で求めることができます。
よって、全体時間計算量O(MlogM + NlogM)
で答えを求めることができます。
public static void Solve()
{
var (N, M, P) = Scanner.Scan<int, int, long>();
var A = Scanner.ScanEnumerable<long>().ToArray();
var B = Scanner.ScanEnumerable<long>().ToArray();
Array.Sort(B);
var cumB = new long[M + 1];
for (var i = 0; i < M; i++)
{
cumB[i + 1] = cumB[i] + B[i];
}
long answer = 0;
for (var i = 0; i < N; i++)
{
var lb = LowerBound(B, Math.Max(0, P - A[i]));
var x = A[i] * lb + cumB[lb];
var y = (M - lb) * P;
answer += x + y;
}
Console.WriteLine(answer);
}
public static int LowerBound<T>(List<T> source, T key) where T : IComparable<T>
=> LowerBound(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(source), key);
public static int LowerBound<T>(ReadOnlySpan<T> source, T key) where T : IComparable<T>
{
var (l, r) = (-1, source.Length);
while (r - l > 1)
{
var m = l + (r - l) / 2;
if (source[m].CompareTo(key) >= 0) r = m;
else l = m;
}
return r;
}
問題E
まだ解けていません。
問題F
次のような動的計画法を解きます。
dp[s] := 現在のクエリまでみたとき、総和がsとなる組み合わせの個数
遷移は次のようになります。
if op == '+'
dp[j] += dp[i][j-x] (j=K,K-1,..,x+1,x)
if op == '-'
dp[j] -= dp[i][j-x] (j=x,x+1,..,K-1,K)
public static void Solve()
{
var (Q, K) = Scanner.Scan<int, int>();
var dp = new mint[K + 1];
dp[0] = 1;
for (var i = 0; i < Q; i++)
{
var (op, x) = Scanner.Scan<char, int>();
if (op == '+')
{
for (var j = K; j >= x; j--)
{
dp[j] += dp[j - x];
}
}
else
{
for (var j = x; j <= K; j++)
{
dp[j] -= dp[j - x];
}
}
var answer = dp[K];
Console.WriteLine(answer);
}
}