selpo's diary

主に競プロの解説をします。C#erです。

問題B『DDPC特別ビュッフェⅡ』 : DDPC(DISCO presents ディスカバリーチャンネル プログラミングコンテスト)2016 本選

冷静に考えればそこまで難しくはないのですが、
朝だと頭が働かず、本番中には解けませんでした。

問題文 : DDPC特別ビュッフェⅡ

概要

N種類の料理があり、i番目の料理は時刻T_iまでしか食べられない。
また、それぞれの料理にはスコアA_iが決まっており、一度食べたらなくなる。
それぞれの時刻(t\geq 1)ではt\leq T_iの料理をひとつ選んで食べることができる。

うまく食べる料理を選ぶとき、スコアの合計をX以上にするのに必要な時間の最小値を出力せよ。
どうやってもX以上のスコアにできなければ、-1を出力せよ。

制約

1\leq N\leq 10^5
1\leq T_i\leq 10^5
1\leq A_i\leq 10^5
1\leq X\leq 10^9

解説

美味しい料理から食べていけばよさそうだが、制限時間の緩い(T_iの大きい)料理は後回しにしたい。
どれくらい後回しにできるか、とか考えるといろいろと面倒(←何回もバグりました)。


しかし後ろから考えると、すっきり解けることが分かる。
つまり、時刻zまでに食べ終えるかを判定して、1\leq z<\max_i T_i+1の範囲でzについて二分探索すればOK。

まず、料理をT_iでバケットソートしておく。
時刻t(t=1,2,\ldots,z-1)において食べるべき料理は、
T_i\geq tを満たす料理のうちA_iが最も大きいもの。
よって、優先度付きキューを用意して、各時刻で、T_i=tの料理をpushし、スコアの和にpopして足せば、
時刻zまでに食べ終えるときのスコアの最大値が分かる。

一回の判定でO(N)回push、popするのでO(N\log N)
二分探索もあわせて、全体では計算量はO(N(\log N)^2)

ソースコード

using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
	static int F() { return int.Parse(Console.ReadLine()); }
	static int[] G() { return Console.ReadLine().Split().Select(_ => int.Parse(_)).ToArray(); }
	static void Main()
	{
		var I = G();
		var N = I[0]; var X = I[1];
		var T = G();
		var A = G();
		var MT = T.Max();
		var a = new List<int>[MT + 1];
		for (var i = 0; i <= MT; i++) a[i] = new List<int>();
		for (var i = 0; i < N; i++) a[T[i]].Add(A[i]);
		// a[i] = 時刻iに食べられる料理たち
		var ans = FirstBinary(1, MT + 1, z =>
		{
			var s = 0L;
			var foods = new PriorityQueue<int>((x, y) => y.CompareTo(x));
			Action<IEnumerable<int>> union = set => { foreach (var item in set) foods.Enqueue(item); };
			for (var t = z + 1; t <= MT; t++) union(a[t]);
			for (var t = z; t >= 1; t--)
			{
				union(a[t]);
				if (foods.Count > 0) s += foods.Dequeue();
			}
			return s >= X;
		});
		Console.WriteLine(ans <= MT ? ans : -1);
	}
	static int FirstBinary(int min, int max, Predicate<int> pred)
	{
		while (min < max)
		{
			var mid = (min + max) >> 1;
			if (pred(mid)) max = mid;
			else min = mid + 1;
		}
		return min;
	}
}

ちなみに、C#には優先度付きキューはないので、自分で実装したものを使っています。