AOJ0246 Bara-Bara Manju

問題リンク Bara-Bara Manju

  • 解法

深さ優先探索+枝刈りが方針です。
何をどう深さ優先探索したのかという話になりますが、ずばり、「10の作り方」です。
10を和で表現する方法は調べてみると41通りあります。この10の作り方それぞれに番号を振り、k番の10の作り方を使って10を作る個数を探索します。枝刈りのために、残っている整数の総和をSとします。すると、どんなに頑張っても残りS/10個しかおまんじゅうを詰めれません。これを使って解が更新できるかをチェックします。
まだ時間が足りません。そこで、(1, 9)(2, 8)(3, 7)(4, 6)(5, 5)というまんじゅうの詰め方を作れるだけ作っておきます。これらはまんじゅうを2個だけ使う事で10を作れます。まんじゅうは使える整数が減れば減るほど10を作りづらくなるので、これらは貪欲に作ってよいのです。

  • ソース
import java.util.Scanner;

//Bara-Bara Manju
public class AOJ0246 {
	
	int res, num, ID;
	int[][] div;
	int[] c, u;
	
	void f(int k, int rest){
		if(rest==0){
			for(int i=1;i<10;i++)div[ID][i] = u[i];
			ID++;
			return;
		}
		if(k<=0)return;
		for(int use=0;use*k<=rest;use++){
			u[k] = use;
			f(k-1, rest-use*k);
			u[k] = 0;
		}
	}
	
	void dfs(int k, int num, int sum){
		res = Math.max(res, num);
		if(k==ID || num+sum/10 <= res)return;
		int t = 1000;
		for(int i=1;i<10;i++)if(div[k][i]>0)t = Math.min(t, c[i]/div[k][i]);
		for(int use=0;use<=t;use++){
			for(int i=1;i<10;i++)c[i]-=use*div[k][i];
			dfs(k+1, num+use, sum-use*10);
			for(int i=1;i<10;i++)c[i]+=use*div[k][i];
		}
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		div = new int[50][11];
		u = new int[11];
		f(9, 10);
		for(;;){
			int n = sc.nextInt();
			if(n==0)break;
			c = new int[11];
			int sum = 0;
			for(int i=0;i<n;i++){
				int x = sc.nextInt();
				c[x]++;
				sum+=x;
			}
			int g = c[5]/2;
			sum-=g*10;
			c[5]%=2;
			for(int i=6;i<10;i++){
				int t = Math.min(c[i], c[10-i]);
				sum-=10*t;
				g+=t;
				c[i]-=t;
				c[10-i]-=t;
			}
			res = 0;
			dfs(0, g, sum);
			System.out.println(res);
		}
	}
	
	public static void main(String[] args) {
		new AOJ0246().run();
	}
}