AOJ1261 Mobile Computing

問題リンク Mobile Computing

  • 概要

モビールというものは以下のように再帰的に定義される。
 1つの石をヒモで吊り下げたもの
 長さ1の棒の両端にモビールを吊り下げたもので、棒の重心の位置にヒモをつけて吊り下げられる
今石がS個あり、それぞれの重さはwiである。これらの石を全て使ってモビールを作るとき、そのモビールの横幅を幅R未満の範囲で最大化せよ。モビール同士が重なってしまうような構築の仕方をしても構わない。石の横幅は無視してよい。
1 <= S <= 6
1 <= wi <= 1000
0.0 < r < 10.0

  • 解法

棒を節点、石を葉とする2分木をモビールと考えることができます。つまり、葉がS個の2分木を全て列挙し、その葉に置く石を全通り試せば答えが出ます。
葉がS個の2分木をどう列挙したか説明します。
文字列で2分木を表現し、それを構文解析してモビールを得るという方法を取りました。2分木は
Exp = (Exp,Exp) | Num
Num = '0'〜'9'
というBNFで表現できます。葉が1〜6個の2分木をこの文字列で列挙、生成します。葉が6個のときでも木は40通りくらいしかありません。そして、石の置き方は6!通りまでなので40*6! ≒ 3000 で、計算時間は大したことはないです。
生成する文字列ではNumに相当する部分を'?'にしておいて、順列を生成したのち石の番号に置き換えています。
あとは、再帰的にモビールのノードを訪問して、石が置かれているx座標の最小値と最大値を調べていきます。

※2分木の根ノードの左側に使う石の集合と、右側に使う石の集合を決めて、左にはみ出す分+右にはみ出す分+1がモビールの横幅になるとは限らないです。棒の重なりが許されているので、左に吊り下げたモビールの左端より右に吊り下げたモビールの左端が左にくることがあるからです。自分はまんまとこれで引っ掛かっていました。

  • ソース
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

//Mobile Computing
public class AOJ1261 {

	double r, best;
	int n;
	int[] ws, w;
	Set<String>[] trees;
	
	class M{
		double x;
		boolean leaf;
		int w;
		M left, right;
		public M(double x, boolean leaf, int w) {
			this.x = x;
			this.leaf = leaf;
			this.w = w;
			left = right = null;
		}
		int get(){
			if(leaf)return w;
			return left.get()+right.get();
		}
		void val(){
			if(leaf){
				min = Math.min(min, x); max = Math.max(max, x); return;
			}
			int w1 = left.get(), w2 = right.get();
			double a = w2*1.0/(w1+w2);
			left.x = x-a;
			right.x = x+1-a;
			left.val();
			right.val();
		}
	}
	
	double min, max;
	boolean[] used;
	int[] order;
	void dfs(int k){
		if(k==n){
			for(String t:trees[n]){
				s = (t+"$").toCharArray();
				id = 0;
				int a = 0;
				//orderに石を置く順番が入っているので'?'を石の番号に置き換える
				for(int i=0;i<s.length;i++){
					if(s[i]=='?'){
						s[i] = (char)(order[a++]+'0');
					}
				}
				//構文解析してモビールを得る
				M root = exp();
				min = max = 0;
				if(!root.leaf){
					root.x = 0;
					root.val();
				}
				double res = max-min;
				if(res<r)best = Math.max(best, res);
				root.val();
			}
			return;
		}
		for(int i=0;i<n;i++){
			if(used[i])continue;
			used[i]=true;
			order[k] = i;
			dfs(k+1);
			used[i]=false;
		}
	}
	
	char[] s;
	int id;
	char get(){
		return s[id++];
	}
	M exp(){
		char ch = get();
		if(ch=='('){
			M res = new M(0, false, 0);
			res.left = exp();
			get();
			res.right = exp();
			get();
			return res;
		}
		M res = new M(0, true, ws[ch-'0']);
		return res;
	}
	
	@SuppressWarnings("unchecked")
	void run(){
		//文字列で木を表現
		trees = new Set[7];
		for(int i=1;i<=6;i++)trees[i] = new HashSet<String>();
		trees[1].add("?");
		for(int i=2;i<=6;i++){
			for(int j=1;j<i;j++){
				for(String left:trees[j])for(String right:trees[i-j])trees[i].add("("+left+","+right+")");
			}
		}
		Scanner sc = new Scanner(System.in);
		int T = sc.nextInt();
		while(T--!=0){
			r = sc.nextDouble(); n = sc.nextInt();
			ws = new int[n];
			for(int i=0;i<n;i++)ws[i]=sc.nextInt();
			w = new int[1<<n];
			for(int i=0;i<1<<n;i++){
				int s = 0;
				for(int j=0;j<n;j++)if(((i>>j)&1)>0)s+=ws[n-1-j];
				w[i] = s;
			}
			best = -1;
			used = new boolean[n];
			order = new int[n];
			dfs(0);
			System.out.printf("%.8f\n", best);
		}
	}

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