Problem:
古本屋(Books)

あなたの町にはJOI 古本店という老舗の古本屋があり，あなたはJOI 古本店をよく利用している．それぞれの本には基準価格が定まっており，JOI 古本店に行けばその価格で買い取ってもらえる．

JOI 古本店では，本を小説,漫画,雑誌など10 種類のジャンルに分類して扱っている．ジャンルには1から10 までの番号が付けられている．JOI 古本店には，同じジャンルの本をまとめて買い取ってもらうと高値で買い取ってくれるというサービスがある．具体的には，同じジャンルの本をまとめてT 冊買い取ってもらう場合，そのジャンルの本の一冊あたりの買取価格が基準価格よりT - 1 円高くなる．例えば，同じジャンルで基準価格100 円，120 円，150 円の本をまとめてJOI 古本店に売ったとすると，買取価格はそれぞれ102 円，122 円，152 円となる．

さて，あなたは一身上の都合で急遽引越しをすることになった．あなたはN 冊の本を持っているが，新しい住居にすべての本を持っていくことは困難なため，N 冊の本のうちK 冊をJOI 古本店に売ることにした．

課題

N 冊の本それぞれの基準価格とジャンルの番号が与えられるとき，合計買取価格の最大値を求めるプログラムを作成せよ．

制限

2 ≤ N ≤ 2000    あなたが持っている本の冊数

1 ≤ K < N    JOI 古本店に売る本の冊数

1 ≤ Ci ≤ 100000 = 105    i 番目の本の基準価格

1 ≤ Gi ≤ 10    i 番目の本のジャンルの番号

入力

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

1 行目には整数N, K が空白を区切りとして書かれており，あなたの持っている本の冊数がN で，そのうちK 冊をJOI 古本店に売ることを表す．

続くN 行にはあなたの持っている本の情報が書かれている．i + 1 行目(1 ≤ i ≤ N) には，整数Ci,Giが空白を区切りとして書かれており，i 番目の本の基準価格がCi で，ジャンルの番号がGi であることを表す．

出力

標準出力に，合計買取価格の最大値を表す整数を1 行で出力せよ．

採点基準

採点用データのうち，

配点の20% 分については，N ≤ 20 を満たす．

配点の20% 分については，すべての本のジャンルは1 または2 である．

配点の10% 分については，これら2 つの条件の両方を満たす．

配点の30% 分については，これら2 つの条件の少なくとも一方を満たす．

入出力の例

入力例

7 4
14 1
13 2
12 3
14 2
8 2
16 3
11 2

出力例

60

この入力例では，2 番目，4 番目，6 番目，7 番目の4 冊の本を売ったとき，ジャンル2 の本の買取価格が1 冊あたり2 円高くなるので，これらの本の買取価格は以下のようになる．

番号

基準価格

ジャンル

買取価格

2
4
6
7

13
14
16
11

2
2
3
2

15
16
16
13

よって合計買取価格は15 + 16 + 16 + 13 = 60 円である．このとき合計買取価格は最大となる．

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

Solution in JAVA (judge verdict: Accepted)


import java.util.*;
import java.io.*;

import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.lang.Math.*;

public class Main {

	int INF = 1 << 28;
	//long INF = 1L << 62;
	double EPS = 1e-10;
	int n, k;
	Books[] books;
	int[][] vals;

	void run() {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt(); k = sc.nextInt();
		books = new Books[10];
		for (int i=0;i<10;i++) books[i] = new Books();
		for (int i=0;i<n;i++) {
			int c = sc.nextInt(), g = sc.nextInt() - 1;
			books[g].add(c);
		}
		for (int i=0;i<10;i++) sort(books[i], new MyComp());
		vals = new int[10][k+1];
		vals[0][0] = 0;
		for (int i=0;i<10;i++) {
			int p = 1, sum = 0;
			for (int v : books[i]) {
				sum += v;
				vals[i][p] = sum + p * (p-1);
				if (p == k) break;
				p++;
			}
		}
		mem = new int[10][k+1];
		for (int[] a : mem) fill(a, -1);
		System.out.println(solve(0, k));

	}
	int[][] mem;
	int solve(int j, int k) {
		if (j == 10 || k == 0) return 0;
		if (mem[j][k] >= 0) return mem[j][k];

		int ret = 0;
		for (int i=0;i<=k;i++) {
			ret = max(ret, solve(j+1, k-i) + vals[j][i]);
		}

		return mem[j][k] = ret;
	}

	class MyComp implements Comparator<Integer> {

		@Override
		public int compare(Integer o1, Integer o2) {
			// TODO ティツ?ェテ・ツ仰陛ァツ板淌ヲツ按静」ツ?陛」ツつ古」ツ?淌」ツδ。テ」ツつステ」ツδε」ツδ嘉」ツδサテ」ツつケテ」ツつソテ」ツδ?
			return o2 - o1;
		}

	}

	class Books extends LinkedList<Integer> {}

	void debug(Object... os) {
		System.err.println(Arrays.deepToString(os));
	}

	public static void main(String[] args) {
		new Main().run();
	}
}