using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics.X86; using System.Text; using YukicoderContest328.Problems; namespace YukicoderContest328.Problems { public class ProblemA : ProblemBase { public ProblemA() : base(false) { } [MethodImpl(MethodImplOptions.AggressiveOptimization)] protected override void SolveEach(IOManager io) { var n = io.ReadInt(); var a = io.ReadLongArray(n); var set = new RedBlackTree(); foreach (var ai in a) { set.Add(ai); } var i = 0; while (set.Count > 1) { if (i % 2 == 0) { var x = set.Min; set.Remove(x); var y = set.Min; set.Remove(y); set.Add(x * y); } else { var y = set.Max; set.Remove(y); var x = set.Min; set.Remove(x); set.Add((x + y - 1) / y); } } io.WriteLine(set.Min); } /// /// Red-Black tree which allows duplicated values (like multiset). /// Based on .NET Runtime https://github.com/dotnet/runtime/blob/master/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs /// /// /// /// .NET Runtime /// Copyright (c) .NET Foundation and Contributors /// Released under the MIT license /// https://github.com/dotnet/runtime/blob/master/LICENSE.TXT public class RedBlackTree : ICollection, IReadOnlyCollection where T : IComparable { public int Count { get; private set; } public bool IsReadOnly => false; protected Node _root; public T this[Index index] { get { var i = index.GetOffset(Count); if (unchecked((uint)i) >= Count) { throw new ArgumentOutOfRangeException(nameof(index)); } var current = _root; while (true) { var leftCount = current.Left?.Count ?? 0; if (leftCount == i) { return current.Item; } else if (leftCount > i) { current = current.Left; } else { i -= leftCount + 1; current = current.Right; } } } } /// /// 最小の要素を返します。要素が空の場合、default値を返します。 /// public T Min { get { if (_root == null) { return default; } else { var current = _root; while (current.Left != null) { current = current.Left; } return current.Item; } } } /// /// 最大の要素を返します。要素が空の場合、default値を返します。 /// public T Max { get { if (_root == null) { return default; } else { var current = _root; while (current.Right != null) { current = current.Right; } return current.Item; } } } #region ICollection members public void Add(T item) { if (_root == null) { _root = new Node(item, NodeColor.Black); Count = 1; return; } Node current = _root; Node parent = null; Node grandParent = null; // 親、祖父はRotateで直接いじる Node greatGrandParent = null; // 曾祖父はRotate後のつなぎ替えで使う(2回Rotateすると曾祖父がcurrentの親になる) var order = 0; while (current != null) { current.Count++; // 部分木サイズ++ order = item.CompareTo(current.Item); if (current.Is4Node) { // 4-node (RBR) の場合は2-node x 2 (BRB) に変更 current.Split4Node(); if (Node.IsNonNullRed(parent)) { // Splitの結果親と2連続でRRになったら修正 InsertionBalance(current, ref parent, grandParent, greatGrandParent); } } greatGrandParent = grandParent; grandParent = parent; parent = current; current = order <= 0 ? current.Left : current.Right; } var newNode = new Node(item, NodeColor.Red); if (order <= 0) { parent.Left = newNode; } else { parent.Right = newNode; } if (parent.IsRed) { // Redの親がRedのときは修正 InsertionBalance(newNode, ref parent, grandParent, greatGrandParent); } _root.Color = NodeColor.Black; // rootは常にBlack(Red->Blackとなったとき木全体の黒高さが1増える) Count++; } public void Clear() { _root = null; Count = 0; } public bool Contains(T item) { var current = _root; while (current != null) { var order = item.CompareTo(current.Item); if (order == 0) { return true; } else { current = order <= 0 ? current.Left : current.Right; } } return false; } public void CopyTo(T[] array, int arrayIndex) { foreach (var value in this) { array[arrayIndex++] = value; } } public bool Remove(T item) { // .NET RuntimeのSortedSetはややトリッキーな実装をしている。 // 値の検索を行う際、検索パスにある全ての2-nodeを3-nodeまたは4-nodeに変更しつつ進んでいくのだが、 // 各ノードに部分木のサイズを持たせたい場合、実装が難しくなるため、一般的な実装を用いることとする。 // (削除が失敗した場合はサイズが変わらず、成功した場合のみサイズが変更となるため、パスを保存しておきたいのだが、 //  木を回転させながら検索を行うと木の親子関係が変化するため、パスも都度変更となってしまう。) var found = false; Node current = _root; var parents = new Stack(2 * Log2(Count + 1)); // 親ノードのスタック parents.Push(null); // 番兵 while (current != null) { parents.Push(current); var order = item.CompareTo(current.Item); if (order == 0) { found = true; break; } else { current = order < 0 ? current.Left : current.Right; } } if (!found) { // 見付からなければreturn return false; } // 子を2つ持つ場合 if (current.Left != null && current.Right != null) { // 右部分木の最小ノードを探す parents.Push(current.Right); var minNode = GetMinNode(current.Right, parents); // この最小値の値だけもらってしまい、あとはこの最小値ノードを削除することを考えればよい current.Item = minNode.Item; current = minNode; } // 通ったパス上にある部分木のサイズを全て1だけデクリメント parents.Pop(); // これは今から消すので不要 Count--; foreach (var node in parents) { if (node != null) { node.Count--; } } // 切り離した部分をくっつける。子を2つ持つ場合については上で考えたため、子を0or1つ持つ場合を考えればよい // 二分木の黒高さが全て等しいという条件より、片方だけ2個以上伸びているということは起こり得ない var parent = parents.Peek(); ReplaceChildOrRoot(parent, current, current.Left ?? current.Right); // L/Rのどちらかnullでない方。どちらもnullならnullが入る // 削除するノードが赤の場合は修正不要 if (current.IsRed) { return true; } // つなぎ替えられた方の子 current = current.Left ?? current.Right; while ((parent = parents.Pop()) != null) { var toFix = DeleteBalance(current, parent, out var newParent); ReplaceChildOrRoot(parents.Peek(), parent, newParent); if (!toFix) { break; } current = newParent; } if (_root != null) { _root.Color = NodeColor.Black; } return true; } private static Node GetMinNode(Node current, Stack parents) { while (current.Left != null) { current = current.Left; parents.Push(current); } return current; } public IEnumerator GetEnumerator() { if (_root != null) { var stack = new Stack(2 * Log2(Count + 1)); PushLeft(stack, _root); while (stack.Count > 0) { var current = stack.Pop(); yield return current.Item; PushLeft(stack, current.Right); } } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); #endregion /// /// 未満の最大の要素を取得します。見付からなかった場合、defaultを返します。 /// public T GetLessThan(T item) { var current = _root; var result = default(T); while (current != null) { var order = current.Item.CompareTo(item); if (order < 0) { result = current.Item; current = current.Right; } else { current = current.Left; } } return result; } /// /// 以上の最小の要素を取得します。見付からなかった場合、defaultを返します。 /// public T GetGreaterEqual(T item) { var current = _root; var result = default(T); while (current != null) { var order = current.Item.CompareTo(item); if (order >= 0) { result = current.Item; current = current.Left; } else { current = current.Right; } } return result; } /// /// [, )を満たす要素を昇順に列挙します。 /// /// 区間の最小値(それ自身を含む) /// 区間の最大値(それ自身を含まない) /// public IEnumerable EnumerateRange(T inclusiveMin, T exclusiveMax) { if (_root != null) { var stack = new Stack(2 * Log2(Count + 1)); var current = _root; while (current != null) { var order = current.Item.CompareTo(inclusiveMin); if (order >= 0) { stack.Push(current); current = current.Left; } else { current = current.Right; } } while (stack.Count > 0) { current = stack.Pop(); var order = current.Item.CompareTo(exclusiveMax); if (order >= 0) { yield break; } else { yield return current.Item; PushLeft(stack, current.Right); } } } } private static void PushLeft(Stack stack, Node node) { while (node != null) { stack.Push(node); node = node.Left; } } private static int Log2(int n) { int result = 0; while (n > 0) { result++; n >>= 1; } return result; } // After calling InsertionBalance, we need to make sure `current` and `parent` are up-to-date. // It doesn't matter if we keep `grandParent` and `greatGrandParent` up-to-date, because we won't // need to split again in the next node. // By the time we need to split again, everything will be correctly set. private void InsertionBalance(Node current, ref Node parent, Node grandParent, Node greatGrandParent) { Debug.Assert(parent != null); Debug.Assert(grandParent != null); var parentIsOnRight = grandParent.Right == parent; var currentIsOnRight = parent.Right == current; Node newChildOfGreatGrandParent; if (parentIsOnRight == currentIsOnRight) { // LL or RRなら1回転でOK newChildOfGreatGrandParent = currentIsOnRight ? grandParent.RotateLeft() : grandParent.RotateRight(); } else { // LR or RLなら2回転 newChildOfGreatGrandParent = currentIsOnRight ? grandParent.RotateLeftRight() : grandParent.RotateRightLeft(); // 1回転ごとに1つ上に行くため、2回転させると曾祖父が親になる // リンク先「挿入操作」を参照 http://wwwa.pikara.ne.jp/okojisan/rb-tree/index.html parent = greatGrandParent; } // 祖父は親の子(1回転)もしくは自分の子(2回転)のいずれかになる // この時点で色がRRBもしくはBRRになっているため、BRBに修正 // リンク先「挿入操作」を参照 http://wwwa.pikara.ne.jp/okojisan/rb-tree/index.html grandParent.Color = NodeColor.Red; newChildOfGreatGrandParent.Color = NodeColor.Black; ReplaceChildOrRoot(greatGrandParent, grandParent, newChildOfGreatGrandParent); } private bool DeleteBalance(Node current, Node parent, out Node newParent) { // 削除パターンは大きく分けて4つ // See: http://wwwa.pikara.ne.jp/okojisan/rb-tree/index.html // currentはもともと黒なので(黒でなければ修正する必要がないため)、兄弟はnullにはなり得ない var sibling = parent.GetSibling(current); if (sibling.IsBlack) { if (Node.IsNonNullRed(sibling.Left) || Node.IsNonNullRed(sibling.Right)) { var parentColor = parent.Color; var siblingRedChild = Node.IsNonNullRed(sibling.Left) ? sibling.Left : sibling.Right; var currentIsOnRight = parent.Right == current; var siblingRedChildIsRight = sibling.Right == siblingRedChild; if (currentIsOnRight != siblingRedChildIsRight) { // 1回転 parent.Color = NodeColor.Black; sibling.Color = parentColor; siblingRedChild.Color = NodeColor.Black; newParent = currentIsOnRight ? parent.RotateRight() : parent.RotateLeft(); } else { // 2回転 parent.Color = NodeColor.Black; siblingRedChild.Color = parentColor; newParent = currentIsOnRight ? parent.RotateLeftRight() : parent.RotateRightLeft(); } return false; } else { var needToFix = parent.IsBlack; parent.Color = NodeColor.Black; sibling.Color = NodeColor.Red; newParent = parent; return needToFix; } } else { if (current == parent.Right) { newParent = parent.RotateRight(); } else { newParent = parent.RotateLeft(); } parent.Color = NodeColor.Red; sibling.Color = NodeColor.Black; DeleteBalance(current, parent, out var newChildOfParent); // 再帰は1回限り ReplaceChildOrRoot(newParent, parent, newChildOfParent); return false; } } /// /// 親ノードの子を新しいものに差し替える。ただし親がいなければrootとする。 /// /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ReplaceChildOrRoot(Node parent, Node child, Node newChild) { if (parent != null) { parent.ReplaceChild(child, newChild); } else { _root = newChild; } } #region Debugging [Conditional("DEBUG")] internal void PrintTree() => PrintTree(_root, 0); [Conditional("DEBUG")] private void PrintTree(Node node, int depth) { const int Space = 6; if (node != null) { PrintTree(node.Right, depth + 1); if (node.IsRed) { Console.ForegroundColor = ConsoleColor.Red; Console.Write(string.Concat(Enumerable.Repeat(' ', Space * depth))); Console.WriteLine($"{node.Item} ({node.Count})"); Console.ResetColor(); } else { Console.Write(string.Concat(Enumerable.Repeat(' ', Space * depth))); Console.WriteLine($"{node.Item} ({node.Count})"); } PrintTree(node.Left, depth + 1); } } [Conditional("DEBUG")] internal void AssertCorrectRedBrackTree() => AssertCorrectRedBrackTree(_root); private int AssertCorrectRedBrackTree(Node node) { if (node != null) { // Redが2つ繋がっていないか? Debug.Assert(!(node.IsRed && Node.IsNonNullRed(node.Left))); Debug.Assert(!(node.IsRed && Node.IsNonNullRed(node.Right))); // 左右の黒高さは等しいか? var left = AssertCorrectRedBrackTree(node.Left); var right = AssertCorrectRedBrackTree(node.Right); Debug.Assert(left == right); if (node.IsBlack) { left++; } return left; } else { return 0; } } #endregion protected enum NodeColor : byte { Black, Red } [DebuggerDisplay("Item = {Item}, Size = {Count}")] protected sealed class Node { public T Item { get; set; } public Node Left { get; set; } public Node Right { get; set; } public NodeColor Color { get; set; } /// 部分木のサイズ public int Count { get; set; } public bool IsBlack => Color == NodeColor.Black; public bool IsRed => Color == NodeColor.Red; public bool Is2Node => IsBlack && IsNullOrBlack(Left) && IsNullOrBlack(Right); public bool Is4Node => IsNonNullRed(Left) && IsNonNullRed(Right); [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateCount() => Count = GetCountOrDefault(Left) + GetCountOrDefault(Right) + 1; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNonNullBlack(Node node) => node != null && node.IsBlack; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNonNullRed(Node node) => node != null && node.IsRed; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNullOrBlack(Node node) => node == null || node.IsBlack; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetCountOrDefault(Node node) => node?.Count ?? 0; // C# 6.0 or later [MethodImpl(MethodImplOptions.AggressiveInlining)] public Node(T item, NodeColor color) { Item = item; Color = color; Count = 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Split4Node() { Color = NodeColor.Red; Left.Color = NodeColor.Black; Right.Color = NodeColor.Black; } // 各種Rotateでは位置関係だけ修正する。色までは修正しない。 // 親になったノード(部分木の根)を返り値とする。 // childとかgrandChildとかは祖父(Rotate前の3世代中一番上)目線での呼び方 public Node RotateLeft() { // 右の子が自分の親になる var child = Right; Right = child.Left; child.Left = this; UpdateCount(); child.UpdateCount(); return child; } public Node RotateRight() { // 左の子が自分の親になる var child = Left; Left = child.Right; child.Right = this; UpdateCount(); child.UpdateCount(); return child; } public Node RotateLeftRight() { var child = Left; var grandChild = child.Right; Left = grandChild.Right; grandChild.Right = this; child.Right = grandChild.Left; grandChild.Left = child; UpdateCount(); child.UpdateCount(); grandChild.UpdateCount(); return grandChild; } public Node RotateRightLeft() { var child = Right; var grandChild = child.Left; Right = grandChild.Left; grandChild.Left = this; child.Left = grandChild.Right; grandChild.Right = child; UpdateCount(); child.UpdateCount(); grandChild.UpdateCount(); return grandChild; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReplaceChild(Node child, Node newChild) { if (Left == child) { Left = newChild; } else { Right = newChild; } } /// /// 兄弟を取得する。 /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Node GetSibling(Node node) => node == Left ? Right : Left; /// /// 左右の2-nodeを4-nodeにマージする。 /// public void Merge2Nodes() { Color = NodeColor.Black; Left.Color = NodeColor.Red; Right.Color = NodeColor.Red; } } } } } namespace YukicoderContest328 { internal class Program { static void Main(string[] args) { IProblem question = new ProblemA(); using var io = new IOManager(Console.OpenStandardInput(), Console.OpenStandardOutput()); question.Solve(io); } } } #region Base Class namespace YukicoderContest328.Problems { public interface IProblem { string Solve(string input); void Solve(IOManager io); } public abstract class ProblemBase : IProblem { protected bool HasMultiTestCases { get; } protected ProblemBase(bool hasMultiTestCases) => HasMultiTestCases = hasMultiTestCases; public string Solve(string input) { var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(input)); var outputStream = new MemoryStream(); using var manager = new IOManager(inputStream, outputStream); Solve(manager); manager.Flush(); outputStream.Seek(0, SeekOrigin.Begin); var reader = new StreamReader(outputStream); return reader.ReadToEnd(); } public void Solve(IOManager io) { var tests = HasMultiTestCases ? io.ReadInt() : 1; for (var t = 0; t < tests; t++) { SolveEach(io); } } protected abstract void SolveEach(IOManager io); } } #endregion #region Utils namespace YukicoderContest328 { public class IOManager : IDisposable { private readonly BinaryReader _reader; private readonly StreamWriter _writer; private bool _disposedValue; private byte[] _buffer = new byte[1024]; private int _length; private int _cursor; private bool _eof; const char ValidFirstChar = '!'; const char ValidLastChar = '~'; public IOManager(Stream input, Stream output) { _reader = new BinaryReader(input); _writer = new StreamWriter(output) { AutoFlush = false }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private char ReadAscii() { if (_cursor == _length) { _cursor = 0; _length = _reader.Read(_buffer); if (_length == 0) { if (!_eof) { _eof = true; return char.MinValue; } else { ThrowEndOfStreamException(); } } } return (char)_buffer[_cursor++]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public char ReadChar() { char c; while (!IsValidChar(c = ReadAscii())) { } return c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadString() { var builder = new StringBuilder(); char c; while (!IsValidChar(c = ReadAscii())) { } do { builder.Append(c); } while (IsValidChar(c = ReadAscii())); return builder.ToString(); } public int ReadInt() => (int)ReadLong(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadLong() { long result = 0; bool isPositive = true; char c; while (!IsNumericChar(c = ReadAscii())) { } if (c == '-') { isPositive = false; c = ReadAscii(); } do { result *= 10; result += c - '0'; } while (IsNumericChar(c = ReadAscii())); return isPositive ? result : -result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private Span ReadChunk(Span span) { var i = 0; char c; while (!IsValidChar(c = ReadAscii())) { } do { span[i++] = c; } while (IsValidChar(c = ReadAscii())); return span.Slice(0, i); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public double ReadDouble() => double.Parse(ReadChunk(stackalloc char[32])); [MethodImpl(MethodImplOptions.AggressiveInlining)] public decimal ReadDecimal() => decimal.Parse(ReadChunk(stackalloc char[32])); public int[] ReadIntArray(int n) { var a = new int[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadInt(); } return a; } public long[] ReadLongArray(int n) { var a = new long[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadLong(); } return a; } public double[] ReadDoubleArray(int n) { var a = new double[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadDouble(); } return a; } public decimal[] ReadDecimalArray(int n) { var a = new decimal[n]; for (int i = 0; i < a.Length; i++) { a[i] = ReadDecimal(); } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(T value) => _writer.Write(value.ToString()); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteLine(T value) => _writer.WriteLine(value.ToString()); public void WriteLine(IEnumerable values, char separator) { var e = values.GetEnumerator(); if (e.MoveNext()) { _writer.Write(e.Current.ToString()); while (e.MoveNext()) { _writer.Write(separator); _writer.Write(e.Current.ToString()); } } _writer.WriteLine(); } public void WriteLine(T[] values, char separator) => WriteLine((ReadOnlySpan)values, separator); public void WriteLine(Span values, char separator) => WriteLine((ReadOnlySpan)values, separator); public void WriteLine(ReadOnlySpan values, char separator) { for (int i = 0; i < values.Length - 1; i++) { _writer.Write(values[i]); _writer.Write(separator); } if (values.Length > 0) { _writer.Write(values[^1]); } _writer.WriteLine(); } public void Flush() => _writer.Flush(); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsValidChar(char c) => ValidFirstChar <= c && c <= ValidLastChar; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsNumericChar(char c) => ('0' <= c && c <= '9') || c == '-'; private void ThrowEndOfStreamException() => throw new EndOfStreamException(); protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { _reader.Dispose(); _writer.Flush(); _writer.Dispose(); } _disposedValue = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } public static class UtilExtensions { public static bool ChangeMax(ref this T value, T other) where T : struct, IComparable { if (value.CompareTo(other) < 0) { value = other; return true; } return false; } public static bool ChangeMin(ref this T value, T other) where T : struct, IComparable { if (value.CompareTo(other) > 0) { value = other; return true; } return false; } public static void SwapIfLargerThan(ref this T a, ref T b) where T : struct, IComparable { if (a.CompareTo(b) > 0) { (a, b) = (b, a); } } public static void SwapIfSmallerThan(ref this T a, ref T b) where T : struct, IComparable { if (a.CompareTo(b) < 0) { (a, b) = (b, a); } } public static void Sort(this T[] array) where T : IComparable => Array.Sort(array); public static void Sort(this T[] array, Comparison comparison) => Array.Sort(array, comparison); } public static class CollectionExtensions { private class ArrayWrapper { #pragma warning disable CS0649 public T[] Array; #pragma warning restore CS0649 } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span AsSpan(this List list) { return Unsafe.As>(list).Array.AsSpan(0, list.Count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,] array, int i) { var width = array.GetLength(1); return MemoryMarshal.CreateSpan(ref array[i, 0], width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,,] array, int i, int j) { var width = array.GetLength(2); return MemoryMarshal.CreateSpan(ref array[i, j, 0], width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,,,] array, int i, int j, int k) { var width = array.GetLength(3); return MemoryMarshal.CreateSpan(ref array[i, j, k, 0], width); } public static void Fill(this T[] array, T value) => array.AsSpan().Fill(value); public static void Fill(this T[,] array, T value) => MemoryMarshal.CreateSpan(ref array[0, 0], array.Length).Fill(value); public static void Fill(this T[,,] array, T value) => MemoryMarshal.CreateSpan(ref array[0, 0, 0], array.Length).Fill(value); public static void Fill(this T[,,,] array, T value) => MemoryMarshal.CreateSpan(ref array[0, 0, 0, 0], array.Length).Fill(value); } public static class SearchExtensions { private struct LowerBoundComparer : IComparer where T : IComparable { public int Compare(T x, T y) => 0 <= x.CompareTo(y) ? 1 : -1; } private struct UpperBoundComparer : IComparer where T : IComparable { public int Compare(T x, T y) => 0 < x.CompareTo(y) ? 1 : -1; } // https://trsing.hatenablog.com/entry/2019/08/27/211038 public static int GetGreaterEqualIndex(this ReadOnlySpan span, T inclusiveMin) where T : IComparable => ~span.BinarySearch(inclusiveMin, new UpperBoundComparer()); public static int GetGreaterThanIndex(this ReadOnlySpan span, T exclusiveMin) where T : IComparable => ~span.BinarySearch(exclusiveMin, new LowerBoundComparer()); public static int GetLessEqualIndex(this ReadOnlySpan span, T inclusiveMax) where T : IComparable => ~span.BinarySearch(inclusiveMax, new LowerBoundComparer()) - 1; public static int GetLessThanIndex(this ReadOnlySpan span, T exclusiveMax) where T : IComparable => ~span.BinarySearch(exclusiveMax, new UpperBoundComparer()) - 1; public static int GetGreaterEqualIndex(this Span span, T inclusiveMin) where T : IComparable => ((ReadOnlySpan)span).GetGreaterEqualIndex(inclusiveMin); public static int GetGreaterThanIndex(this Span span, T exclusiveMin) where T : IComparable => ((ReadOnlySpan)span).GetGreaterThanIndex(exclusiveMin); public static int GetLessEqualIndex(this Span span, T inclusiveMax) where T : IComparable => ((ReadOnlySpan)span).GetLessEqualIndex(inclusiveMax); public static int GetLessThanIndex(this Span span, T exclusiveMax) where T : IComparable => ((ReadOnlySpan)span).GetLessThanIndex(exclusiveMax); public static int BoundaryBinarySearch(Predicate predicate, int ok, int ng) { while (Math.Abs(ok - ng) > 1) { var mid = (ok + ng) / 2; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return ok; } public static long BoundaryBinarySearch(Predicate predicate, long ok, long ng) { while (Math.Abs(ok - ng) > 1) { var mid = (ok + ng) / 2; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return ok; } public static BigInteger BoundaryBinarySearch(Predicate predicate, BigInteger ok, BigInteger ng) { while (BigInteger.Abs(ok - ng) > 1) { var mid = (ok + ng) / 2; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return ok; } public static double BoundaryBinarySearch(Predicate predicate, double ok, double ng, double eps = 1e-9, int loopLimit = 1000) { var count = 0; while (Math.Abs(ok - ng) > eps && count++ < loopLimit) { var mid = (ok + ng) * 0.5; if (predicate(mid)) { ok = mid; } else { ng = mid; } } return (ok + ng) * 0.5; } public static double Bisection(Func f, double a, double b, double eps = 1e-9, int loopLimit = 100) { double mid = (a + b) / 2; var fa = f(a); if (fa * f(b) >= 0) { throw new ArgumentException("f(a)とf(b)は異符号である必要があります。"); } for (int i = 0; i < loopLimit; i++) { var fmid = f(mid); var sign = fa * fmid; if (sign < 0) { b = mid; } else if (sign > 0) { a = mid; fa = fmid; } else { return mid; } mid = (a + b) / 2; if (Math.Abs(b - a) < eps) { break; } } return mid; } } } #endregion