Problem:
MathJax.Hub.Config({ tex2jax: { inlineMath: [["$","$"], ["\\(","\\)"]], processEscapes: true }});

IOI 饅頭(IOI Manju)

Incredible Okashi Inc. は「途方もなくおいしいお菓子(incredible okashi)」を製作している会社である．ここでは略してIOI 社と呼ぶ．IOI 社は特製のIOI 饅頭を作ったので，それを販売することになった．IOI 社は$M$ 種類の饅頭を1 個ずつ作った．作られた$M$ 個の饅頭はすべて同じ大きさであるが，ひとつひとつ異なる味なので価格は様々であり，$i$ 番目$(1 \leq i \leq M)$ の饅頭の価格は$P_i$ 円である．

ところで，あなたはJust Odd Inventions 社を知っているだろうか？ この会社の業務は「ただ奇妙な発明(just odd inventions)」をすることである．ここでは略してJOI 社と呼ぶ．IOI 社は，饅頭を詰めるための高級な箱をJOI 社に発注することになった．JOI 社の製作する饅頭用の箱は$N$ 種類あり， $j$ 番目$(1 \leq j \leq N)$の箱は最大で$C_j$ 個の饅頭を詰められる大きさであり，販売価格は$E_j$ 円である．これらの$N$ 種類の箱のうちの何種類か(0 種類以上$N$ 種類以下) を1 個ずつ発注し，饅頭をそれらの箱に詰め分けてセットで販売することになった．各饅頭セットの価格は，それに含まれる饅頭の価格の合計である．

すべての饅頭セットが売れるとした場合，IOI 社が得ることができる利益の最大値はいくらだろうか．ここで利益とは，販売した饅頭セットの価格の合計から，発注した箱の価格の合計を引いた値である．なお，箱に詰めなかった饅頭については，IOI 社のスタッフがおいしくいただくため，利益には影響しないものとする．

課題

各饅頭の価格と，各箱の大きさと価格が与えられたとき，IOI 社が得ることができる利益の最大値を求めるプログラムを作成せよ．

入力

標準入力から以下のデータを読み込め．

1 行目には，整数 $M, N$ が空白を区切りとして書かれており，饅頭が $M$ 個，箱が $N$ 種類あることを表す．

続く $M$ 行のうちの $i$ 行目 $(1 \leq i \leq M)$ には，整数 $P_i$ が書かれており，$i$ 番目の饅頭の価格が $P_i$ 円であることを表す．

続く$N$ 行のうちの $j$ 行目 $(1 \leq j \leq N)$ には，整数 $C_j$, $E_j$ が空白を区切りとして書かれており， $j$ 番目の箱は最大で $C_j$ 個の饅頭を詰められる大きさであり，価格が $E_j$ 円であることを表す．

出力

標準出力に，IOI 社が得られる利益の最大値を円単位で表す整数を1 行で出力せよ．

制限

すべての入力データは以下の条件を満たす．

$1 \leq M \leq 10000$

$1 \leq N \leq 500$

$1 \leq P_i \leq 10000 (1 \leq i \leq M)$

$1 \leq C_j \leq 10000 (1 \leq j \leq N)$

$1 \leq E_j \leq 10000 (1 \leq j \leq N)$

入出力例

入力例 1

4 3
180
160
170
190
2 100
3 120
4 250

出力例 1

480

この例では，1 番目の箱(100 円) と2 番目の箱(120 円) を発注し，たとえば1 番目の箱に1 番目の饅頭と2 番目の饅頭を詰めて $180 + 160 = 340$ 円のセットとして販売，2 番目の箱に3 番目の饅頭と4 番目の饅頭を詰めて $170 + 190 = 360$ 円のセットとして販売すると，IOI 社の利益は $700 - 220 = 480$ 円となる．

入力例 2

2 2
1000
2000
1 6666
1 7777

出力例 2

0

この例では，利益を最大化するためには箱を全く買わないのがよい．

入力例 3

10 4
200
250
300
300
350
400
500
300
250
200
3 1400
2 500
2 600
1 900

出力例 3

450

問題文と自動審判に使われるデータは、情報オリンピック日本委員会が作成し公開している問題文と採点用テストデータです。

Solution in JAVA (judge verdict: Accepted)

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Main {
	static boolean debug = true;

	public static void main(String[] args) throws IOException {

		UserScanner scan = new UserScanner(System.in);
		PrintWriter pwriter = new PrintWriter(System.out);

		int m = scan.nextInt();
		int n = scan.nextInt();
		Manju manju = new Manju(m, n);
		for (int i = 0; i < m; i++)
			manju.addManju(i, scan.nextInt());
		for (int i = 0; i < n; i++)
			manju.addBox(i, scan.nextInt(), scan.nextInt());

		pwriter.println(manju.getMaxProfit());

		pwriter.flush();

		scan.close();
		System.exit(0);
	}
}

class Manju {
	int[] pr; // price of manju
	int[] ag; // sub total price of manju's ordered by expense

	class Case {
		public Case(int c, int e) {
			ca = c;
			ex = e;
			exu = ex / ca;
		}

		int ca; // capacity of case
		int ex; // expense of case
		double exu; // expense per one manju
	}

	Case[] cs;
	int[][] hs;

	public Manju(int m, int n) {
		pr = new int[m];
		ag = new int[m];
		cs = new Case[n];
		hs = new int[n][m];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				hs[i][j] = -1;
	}

	class caseComp implements Comparator<Case> {

		@Override
		public int compare(Case o1, Case o2) {
			if (o1.exu > o2.exu)
				return 1;
			else if (o1.exu == o2.exu)
				return 0;
			else
				return -1;
		}

	}

	public int getMaxProfit() {
		// sort natural order and rearrange descending order
		Arrays.sort(pr);
		for (int i = 0, j = pr.length - 1; j > i; i++, j--) {
			int tmp = pr[i];
			pr[i] = pr[j];
			pr[j] = tmp;
		}
		setAggrigatePrice();

		Arrays.sort(cs, new caseComp());

		return getMaxPf(0, 0);
	}

	private void setAggrigatePrice() {
		ag[0] = pr[0];
		for (int i = 1; i < pr.length; i++)
			ag[i] = ag[i - 1] + pr[i];
	}

	private int getMaxPf(int ci, int mj) {
		// 'ci' is case id which are ordered by cheaper
		// 'mj' is number of manju, manju are always apply the higher price
		// return max profit by using cases after 'ci'

		// caution: 'ci' is index, 'mj' is number(length)
		if (ci >= cs.length || mj >= pr.length)
			return 0;

		// if next case expense is larger than manju price, it's not necessary
		// to check anymore
		if (cs[ci].exu > pr[mj])
			return 0;

		// history check
		if (hs[ci][mj] >= 0)
			return hs[ci][mj];

		// return greater value case 'ci' is applied or not
		int ret = Math.max(getMaxPf(ci + 1, mj), getProfit(ci, mj) + getMaxPf(ci + 1, mj + cs[ci].ca));
		hs[ci][mj] = ret;
		return ret;
	}

	private int getProfit(int ci, int mj) {
		// return profit for case 'ci' using manju from 'mj'
		int to = Math.min(mj + cs[ci].ca - 1, pr.length - 1);
		if (mj == 0)
			return ag[to] - cs[ci].ex;
		else
			return ag[to] - ag[mj - 1] - cs[ci].ex;
	}

	public void addBox(int i, int c, int e) {
		cs[i] = new Case(c, e);
	}

	public void addManju(int i, int p) {
		this.pr[i] = p;
	}

}

class UserScanner {
	private InputStream in;
	private final byte[] buffer = new byte[1024];
	private int ptr = 0;
	private int buflen = 0;

	public UserScanner(InputStream inStream) {
		in = inStream;
	}

	private void read() {
		ptr = 0;
		try {
			buflen = in.read(buffer);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(9);
		}
	}

	private byte getByte() {
		if (ptr >= buflen)
			read();
		if (buflen < 0 || isCtlSpace(buffer[ptr])) {
			return -1;
		} else
			return buffer[ptr++];
	}

	private void skipCtlSpace() {
		for (; ptr < buflen; ptr++)
			if (!isCtlSpace(buffer[ptr]))
				return;
		read();
		skipCtlSpace();
	}

	private boolean isCtlSpace(byte b) {
		if (b <= ' ' || b > '~')
			return true;
		else
			return false;
	}

	public void close() {
		try {
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(9);
		}
	}

	public String next() {
		skipCtlSpace();
		StringBuilder sb = new StringBuilder();
		byte b;
		while ((b = getByte()) != -1) {
			sb.appendCodePoint(b);
		}
		return sb.toString();
	}

	public int nextInt() {
		skipCtlSpace();
		int n = 0;
		boolean minus = false;
		byte b;
		while ((b = getByte()) != -1) {
			if (b == '-')
				minus = true;
			else {
				n *= 10;
				n += (b - '0');
			}
		}
		if (minus)
			return n * -1;
		else
			return n;
	}
}