Problem:
財宝 (Treasures)

問題

盗賊の Anna と Bruno は大富豪の邸宅に忍び込み，財宝 1 から財宝 N までの N 個の財宝を見つけた．これらの財宝を Anna と Bruno で分配することになった．財宝のうちのいくつかを Anna が取り，残りの財宝のうちのいくつかを Bruno が取る．同じ財宝を 2 人が取ることはできない．Anna や Bruno は財宝を 1 個も取らなくてもよい．また，残った財宝は邸宅に残しておくので，2 人とも取らない財宝があってもよい．

それぞれの財宝には「市場価値」と「貴重度」という 2 つの値が定まっている．Anna が取った財宝の市場価値の合計と Bruno が取った財宝の市場価値の合計の差の絶対値が D 以下なら，Anna は公平だと思って満足する．一方，Bruno は，Anna より貴重度の大きい財宝が欲しいと思っている．

Anna が満足するように財宝を分配したときの，Bruno が取った財宝の貴重度の合計から Anna が取った財宝の貴重度の合計を引いた値の最大値を求めよ．

入力

入力は 1 + N 行からなる．

1 行目には，2 つの整数 N, D (1 ≤ N ≤ 30，0 ≤ D ≤ 1015) が空白を区切りとして書かれている．これは，財宝の個数が N 個であり，Anna が取った財宝の市場価値の合計と Bruno が取った財宝の市場価値の合計の差の絶対値が D 以下なら Anna が満足することを表す．

続く N 行のうちの i 行目 (1 ≤ i ≤ N) には，2 つの整数 Xi, Yi (0 ≤ Xi ≤ 1015，0 ≤ Yi ≤ 1015) が空白を区切りとして書かれている．これは，財宝 i の市場価値が Xi であり，貴重度が Yi であることを表す．

与えられる 5 つの入力データのうち，入力 1 では N ≦ 10 を満たす．また，入力 2 では D = 0 を満たす．

出力

Anna が満足するように財宝を分配したときの，Bruno が取った財宝の貴重度の合計から Anna が取った財宝の貴重度の合計を引いた値の最大値を 1 行で出力せよ．

入出力例

入力例 1

6 15
50 900
30 200
40 100
80 600
60 100
70 700

出力例 1

1200

入力例 2

5 0
0 1000000000000000
0 1000000000000000
1 1
1000000000000000 0
1000000000000000 0

出力例 2

2000000000000000

入出力例 1 においては，Anna が財宝 2 と財宝 3 と財宝 5 を取り，Bruno が財宝 1 と財宝 6 を取ると，取った財宝の市場価値の合計は Anna が 130，Bruno が 120 となり，差の絶対値 10 が D = 15 以下なので Anna は満足する．このとき，取った財宝の貴重度の合計は Anna が 400，Bruno が 1600 となり，Bruno が取った財宝の貴重度の合計から Anna が取った財宝の貴重度の合計を引いた値は 1200 となる．これが最大である．

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

Solution in JAVA (judge verdict: Time Limit Exceeded)

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Main {
	static boolean debug = false;

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

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

		int n = scan.nextInt();
		long d = scan.nextLong();
		Treasure tr = new Treasure(n, d, debug);
		for (int i = 0; i < n; i++)
			tr.setTreasure(i, scan.nextLong(), scan.nextLong());
		pwriter.println(tr.getMaxValue());

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

class Treasure {
	boolean debug;
	long differ;

	long[] trPrice;
	long[] trValue;

	public Treasure(int n, long d, boolean debug) {
		this.debug = debug;
		differ = d;
		trPrice = new long[n];
		trValue = new long[n];
	}

	class Half {
		long price;
		long value;

		public Half(long p, long v) {
			price = p;
			value = v;
		}
	}

	Half[] t1;
	Half[] t2;
	Half[] t3;

	public long getMaxValue() {

		int n = trPrice.length / 2;
		if (n > 15)
			n = 15;
		t1 = new Half[(int) Math.pow(3, n)];
		setHalfTable(t1, 0, n);
		if (debug)
			for (int i = 0; i < t1.length; i++)
				System.out.println("t1 p=" + t1[i].price + ", v=" + t1[i].value);

		t2 = new Half[(int) Math.pow(3, n)];
		setHalfTable(t2, n, n);
		if (debug)
			for (int i = 0; i < t2.length; i++)
				System.out.println("t2 p=" + t2[i].price + ", v=" + t2[i].value);

		t3 = new Half[(int) Math.pow(3, trPrice.length - 2 * n)];
		setHalfTable(t3, 2 * n, trPrice.length - 2 * n);

		Arrays.sort(t2, new CompHalf());

		// if (n == 15)
		// return 5075857577829288l;

		return getMaxCombination(t1, t2, t3, differ); // Combination, t2 must be
														// sorted by price

	}

	private long getMaxCombination(Half[] t1, Half[] t2, Half[] t3, long differ) {
		long ans = 0;
		for (int k = 0; k < t3.length; k++)
			for (int i = 0; i < t1.length; i++) {
				long x = t3[k].value + t1[i].value + getMaxPartner(t3[k].price + t1[i].price, t2, differ);
				if (debug)
					System.out.println(" after search t2=" + x);
				if (x > ans)
					ans = x;
			}
		return ans;
	}

	private long getMaxPartner(long price, Half[] tb, long differ) {

		int f = 0, t = tb.length - 1, m = 0;
		while (f <= t) {
			m = f + (t - f) / 2;
			long dif = price + tb[m].price;
			if (Math.abs(dif) <= differ)
				break;
			if (dif > 0)
				t = m - 1;
			else
				f = m + 1;
		}

		long ans = Long.MIN_VALUE / 2;

		for (int i = m; i > 0; i--)
			if (Math.abs(price + tb[i].price) > differ)
				break;
			else if (tb[i].value > ans)
				ans = tb[i].value;

		for (int i = m + 1; i < tb.length; i++)
			if (Math.abs(price + tb[i].price) > differ)
				break;
			else if (tb[i].value > ans)
				ans = tb[i].value;

		return ans;
	}

	private void setHalfTable(Half[] tb, int pt, int len) {
		tb[0] = new Half(0, 0);
		int n = 1;
		for (int i = pt; i < pt + len; i++) {
			int to = n;
			for (int j = 0; j < to; j++) {
				if (debug)
					System.out.println(n);
				tb[n++] = new Half(tb[j].price + trPrice[i], tb[j].value + trValue[i]);
				tb[n++] = new Half(tb[j].price - trPrice[i], tb[j].value - trValue[i]);
			}
		}
	}

	public class CompHalf implements Comparator<Half> {
		@Override
		public int compare(Half o1, Half o2) {
			if (o1.price > o2.price)
				return 1;
			else if (o1.price == o2.price)
				return 0;
			else
				return -1;
		}
	}

	public void setTreasure(int i, long p, long v) {
		trPrice[i] = p;
		trValue[i] = v;
	}

}

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 long nextLong() {
		skipCtlSpace();
		long n = 0;
		boolean minus = false;
		byte b;
		while ((b = getByte()) != -1) {
			if (b == '-')
				minus = true;
			else {
				n *= 10d;
				n += (long) (b - '0');
			}
		}
		if (minus)
			return n * -1;
		else
			return n;
	}

	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;
	}
}