using System; using System.Collections.Generic; using System.Linq; // https://atcoder.jp/contests/past202107-open/tasks/past202107_o class Program { static string InputPattern = "InputX"; static List<string> GetInputList() { var WillReturn = new List<string>(); if (InputPattern == "Input1") { WillReturn.Add("3"); WillReturn.Add("5 1"); WillReturn.Add("2 4"); WillReturn.Add("4 6"); //4 } else if (InputPattern == "Input2") { WillReturn.Add("3"); WillReturn.Add("3 2"); WillReturn.Add("1 2"); WillReturn.Add("3 6"); //-1 } else { string wkStr; while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr); } return WillReturn; } static void Main() { var TestData = new List<string>(); TestData.Add("3"); TestData.Add("7 1"); TestData.Add("1 5"); TestData.Add("5 8"); long Res = Solve(TestData); Console.WriteLine(Res); return; var InsRandom = new Random(); // 長さと値がランダムなListを作成 for (int I = 1; I <= 3000; I++) { var RandList = new List<string>(); int N = InsRandom.Next(1, 4); RandList.Add(N.ToString()); for (int J = 1; J <= N; J++) { int Rand1 = InsRandom.Next(1, 10); int Rand2 = InsRandom.Next(1, 10); RandList.Add(string.Format("{0} {1}", Rand1, Rand2)); } long Result1 = SolveNaive(RandList); long Result2 = Solve(RandList); if (Result1 != Result2) { Console.WriteLine("■■■差異あり■■■"); Console.WriteLine("愚直={0},高速={1}", Result1, Result2); RandList.ForEach(pX => Console.WriteLine(pX)); } } } // 愚直解 static long SolveNaive(List<string> pInputList) { mNaiveABInfoList.Clear(); long[] wkArr = { }; Action<string> SplitAct = pStr => wkArr = pStr.Split(' ').Select(pX => long.Parse(pX)).ToArray(); foreach (string EachStr in pInputList.Skip(1)) { SplitAct(EachStr); NaiveABInfo WillAdd; WillAdd.A = wkArr[0]; WillAdd.B = wkArr[1]; mNaiveABInfoList.Add(WillAdd); } int UB = mNaiveABInfoList.Count - 1; var BSet = new HashSet<long>(mNaiveABInfoList.Select(pX => pX.B)); var Stk = new Stack<JyoutaiDef>(); JyoutaiDef WillPush; WillPush.CurrComputer = 0; WillPush.Money = 0; WillPush.CurrInd = 0; Stk.Push(WillPush); var AnswerList = new List<long>(); while (Stk.Count > 0) { JyoutaiDef Popped = Stk.Pop(); int CurrInd = Popped.CurrInd; // クリア判定 if (Popped.CurrInd > UB) { AnswerList.Add(Popped.Money); continue; } long CurrMoney = Popped.Money + mNaiveABInfoList[CurrInd].A; if (Popped.CurrComputer >= mNaiveABInfoList[CurrInd].B) { WillPush.CurrComputer = Popped.CurrComputer; WillPush.Money = CurrMoney; WillPush.CurrInd = Popped.CurrInd + 1; Stk.Push(WillPush); } else { foreach (long EachB in BSet) { if (mNaiveABInfoList[CurrInd].B <= EachB) { WillPush.CurrComputer = EachB; WillPush.Money = CurrMoney - EachB; WillPush.CurrInd = Popped.CurrInd + 1; if (WillPush.Money >= 0) { Stk.Push(WillPush); } } } } } if (AnswerList.Count == 0) { return -1; } return AnswerList.Max(); } struct NaiveABInfo { internal long A; internal long B; } static List<NaiveABInfo> mNaiveABInfoList = new List<NaiveABInfo>(); struct JyoutaiDef { internal long CurrComputer; internal long Money; internal int CurrInd; } class ABInfoDef { internal bool IsMorning; // 朝か? internal long Val; } static List<ABInfoDef> mABInfoList1 = new List<ABInfoDef>(); static List<ABInfoDef> mABInfoList2 = new List<ABInfoDef>(); static long Solve(List<string> pInputList) { mABInfoList1.Clear(); mABInfoList2.Clear(); long[] wkArr = { }; Action<string> SplitAct = pStr => wkArr = pStr.Split(' ').Select(pX => long.Parse(pX)).ToArray(); foreach (string EachStr in pInputList.Skip(1)) { SplitAct(EachStr); var WillAdd1 = new ABInfoDef(); WillAdd1.IsMorning = true; WillAdd1.Val = wkArr[0]; mABInfoList1.Add(WillAdd1); var WillAdd2 = new ABInfoDef(); WillAdd2.IsMorning = false; WillAdd2.Val = wkArr[1]; mABInfoList1.Add(WillAdd2); } // コンピュータの価値の累積Maxを更新したら、コンピュータを購入可能 long RunMax = long.MinValue; for (int I = 0; I <= mABInfoList1.Count - 1; I++) { if (mABInfoList1[I].IsMorning) { mABInfoList2.Add(mABInfoList1[I]); continue; } if (RunMax < mABInfoList1[I].Val) { RunMax = mABInfoList1[I].Val; mABInfoList2.Add(mABInfoList1[I]); } } // 連続したMはまとめる for (int I = mABInfoList2.Count - 1; 0 <= I; I--) { if (I > 0) { if (mABInfoList2[I].IsMorning && mABInfoList2[I - 1].IsMorning) { mABInfoList2[I - 1].Val += mABInfoList2[I].Val; mABInfoList2.RemoveAt(I); } } } foreach (ABInfoDef EachABInfo in mABInfoList2) { Console.WriteLine("IsMorning={0},Val={1}", EachABInfo.IsMorning, EachABInfo.Val); } // 稼いだ金額の累積和 long[] RunSum = new long[mABInfoList2.Count]; for (int I = 0; I <= mABInfoList2.Count - 1; I++) { if (mABInfoList2[I].IsMorning) { RunSum[I] = mABInfoList2[I].Val; } else { RunSum[I] = 0; } if (I > 0) { RunSum[I] += RunSum[I - 1]; } } // SegmentTreeクラス (RMQ and 1点更新) var InsSegmentTree = new SegmentTree(mABInfoList2.Count - 1, long.MaxValue); InsSegmentTree.Update(0, 0); for (int I = 0; I <= mABInfoList2.Count - 1; I++) { if (mABInfoList2[I].IsMorning) continue; int ResultInd = ExecNibunhou_LowerBound(mABInfoList2[I].Val, RunSum); Console.WriteLine("ResultInd={0}", ResultInd); if (ResultInd == -1 || ResultInd > I) { return -1; } long RangeMin = ResultInd; long RangeMax = I; if (RangeMin > 0) { RangeMin--; } long MinVal; long MinValInd; Console.WriteLine("セグ木のスキャン区間は[{0},{1}]", RangeMin, RangeMax); InsSegmentTree.GetMinInfo_Left(RangeMin, RangeMax, out MinVal, out MinValInd); if (MinVal == long.MaxValue) { return -1; } long Rest = RunSum[ResultInd] - MinVal; Console.WriteLine("Rest={0}", Rest); if (Rest < mABInfoList2[I].Val) { return -1; } long SetVal = MinVal + mABInfoList2[I].Val; Console.WriteLine("セグ木のInd{0}に{1}をセットします", I, SetVal); InsSegmentTree.Update(I, SetVal); } long AllASum = mABInfoList2.Where(pX => pX.IsMorning).Sum(pX => pX.Val); for (int I = mABInfoList2.Count - 1; 0 <= I; I--) { if (mABInfoList2[I].IsMorning == false) { long CurrVal = InsSegmentTree.Internal_Query(I, I); return AllASum - CurrVal; } } return -1; } // 二分法で、Val以上で最小の値を持つ、添字を返す static int ExecNibunhou_LowerBound(long pVal, long[] pArr) { if (pArr.Length == 0) return -1; // 最後の要素がVal未満の特殊ケース if (pVal > pArr.Last()) { return -1; } // 最初の要素がVal以上の特殊ケース if (pVal <= pArr[0]) { return 0; } int L = 0; int R = pArr.GetUpperBound(0); while (L + 1 < R) { int Mid = (L + R) / 2; if (pArr[Mid] >= pVal) { R = Mid; } else { L = Mid; } } return R; } } #region SegmentTree // SegmentTreeクラス (RMQ and 1点更新) internal class SegmentTree { private long[] mTreeNodeArr; private long UB; // 木のノードの配列のUB private long mLeafCnt; // 葉ノードの数 private long mExternalArrUB; // 拡張機能 (最小値と、最小値を持つIndを返す) // 最小値が複数あったら、左側のIndを優先 internal void GetMinInfo_Left(long pSearchStaInd, long pSearchEndInd, out long pMinVal, out long pMinValInd) { // 区間の最小値を求める pMinVal = Internal_Query(pSearchStaInd, pSearchEndInd); // 二分探索を行う long L = pSearchStaInd, R = pSearchEndInd; while (L + 1 < R) { long Mid = (L + R) / 2; long LeftMin = Internal_Query(L, Mid); if (LeftMin == pMinVal) R = Mid; else L = Mid; } pMinValInd = ((Internal_Query(L, L) == pMinVal) ? L : R); } // 拡張機能 (最小値と、最小値を持つIndを返す) // 最小値が複数あったら、右側のIndを優先 internal void GetMinInfo_Right(long pSearchStaInd, long pSearchEndInd, out long pMinVal, out long pMinValInd) { // 区間の最小値を求める pMinVal = Internal_Query(pSearchStaInd, pSearchEndInd); // 二分探索を行う long L = pSearchStaInd, R = pSearchEndInd; while (L + 1 < R) { long Mid = (L + R) / 2; long RightMin = Internal_Query(Mid, R); if (RightMin == pMinVal) L = Mid; else R = Mid; } pMinValInd = ((Internal_Query(R, R) == pMinVal) ? R : L); } // ノードの添字を引数とし、範囲の開始添字と終了添字を持つ配列 private struct RangeInfoDef { internal long StaInd; internal long EndInd; } private RangeInfoDef[] mRangeInfo; // ノードのIndexの列挙を返す internal IEnumerable<long> GetNodeIndEnum() { for (long I = 0; I <= mExternalArrUB; I++) { yield return I; } } // 木のノードのUBを返す internal long GetUB() { return mExternalArrUB; } // コンストラクタ internal SegmentTree(long pExternalArrUB, long pInitVal) { mExternalArrUB = pExternalArrUB; // 簡単のため、葉ノード数を2のべき乗に long ArrLength = 0; for (long I = 1; I < long.MaxValue; I *= 2) { ArrLength += I; mLeafCnt = I; if (pExternalArrUB + 1 < mLeafCnt) break; } // すべての値をpInitValに UB = ArrLength - 1; mTreeNodeArr = new long[UB + 1]; for (long I = 0; I <= UB; I++) { mTreeNodeArr[I] = pInitVal; } // ノードの添字を引数とし、範囲の開始添字と終了添字を持つ配列の作成 mRangeInfo = new RangeInfoDef[UB + 1]; for (long I = 0; I <= UB; I++) { if (I == 0) { RangeInfoDef WillSet1; WillSet1.StaInd = 0; WillSet1.EndInd = mLeafCnt - 1; mRangeInfo[I] = WillSet1; continue; } long ParentNode = DeriveParentNode(I); RangeInfoDef ParentRangeInfo = mRangeInfo[ParentNode]; RangeInfoDef WillSet2; long Mid = (ParentRangeInfo.StaInd + ParentRangeInfo.EndInd) / 2; if (I % 2 == 1) { // 奇数ノードの場合 WillSet2.StaInd = ParentRangeInfo.StaInd; WillSet2.EndInd = Mid; } else { // 偶数ノードの場合 WillSet2.StaInd = Mid + 1; WillSet2.EndInd = ParentRangeInfo.EndInd; } mRangeInfo[I] = WillSet2; } } // 親ノードの添字を取得 private long DeriveParentNode(long pTarget) { return (pTarget - 1) / 2; } // 子ノードの添字(小さいほう)を取得 private long DeriveChildNode(long pTarget) { return pTarget * 2 + 1; } // 葉ノードの配列の添字を木の添字に変換して返す private long DeriveTreeNode(long pLeafArrInd) { long BaseInd = UB - mLeafCnt + 1; return BaseInd + pLeafArrInd; } // 葉ノードの配列のK番目の値をNewValに変更 internal void Update(long pK, long pNewVal) { long CurrNode = DeriveTreeNode(pK); mTreeNodeArr[CurrNode] = pNewVal; // 登りながら更新 while (CurrNode > 0) { CurrNode = DeriveParentNode(CurrNode); long ChildNode1 = DeriveChildNode(CurrNode); long ChildNode2 = ChildNode1 + 1; mTreeNodeArr[CurrNode] = Math.Min(mTreeNodeArr[ChildNode1], mTreeNodeArr[ChildNode2]); } } // 開始添字と終了添字とカレントノードを引数として、最小値を返す internal long Internal_Query(long pSearchStaInd, long pSearchEndInd) { return Private_Query(pSearchStaInd, pSearchEndInd, 0); } private long Private_Query(long pSearchStaInd, long pSearchEndInd, long pCurrNode) { long CurrNodeStaInd = mRangeInfo[pCurrNode].StaInd; long CurrNodeEndInd = mRangeInfo[pCurrNode].EndInd; // OverLapしてなければ、long.MaxValue if (CurrNodeEndInd < pSearchStaInd || pSearchEndInd < CurrNodeStaInd) return long.MaxValue; // 完全に含んでいれば、このノードの値 if (pSearchStaInd <= CurrNodeStaInd && CurrNodeEndInd <= pSearchEndInd) return mTreeNodeArr[pCurrNode]; // そうでなければ、2つの子の最小値 long ChildNode1 = DeriveChildNode(pCurrNode); long ChildNode2 = ChildNode1 + 1; long ChildVal1 = Private_Query(pSearchStaInd, pSearchEndInd, ChildNode1); long ChildVal2 = Private_Query(pSearchStaInd, pSearchEndInd, ChildNode2); return Math.Min(ChildVal1, ChildVal2); } internal void DebugPrint() { for (long I = 0; I <= UB; I++) { Console.WriteLine("mTreeNodeArr[{0}] = {1}", I, mTreeNodeArr[I]); } } } #endregion