AOJ2028 Gather on the Clock

問題リンク Gather on the Clock

  • 概要

N個の数字が円形にならんでいる。好きな数字Aを1つ選び、時計回りに1つ回った次の数字Bとの差の絶対値|A-B|が得点に加算される。この後、Bは円から無くなる。この操作を円に残った数字がただ1つになるまで続けたとき、得られる得点の最大値を答えよ。
2 <= N <= 100
0 <= 数字 <= 100

  • 解法

メモ化再帰で解きました。
dp[i][j]: 円のi番目の数字をj番目の数字まで動かしたときに得られる最大値
という表を用意します。dp[i][(i-1+N)%N]は、i番目の数字をすぐ後ろの場所まで動かしているので、円に残る最後の数字をiとしたときのスコアの最大値が求まっていることになります。答えとしては、これを全通り試してそのうちの最大値を取ったものになります。
dp[i][j]の埋め方の説明です。
i == jなら0です
(i+1)%N == jなら、iを1回動かすだけなのでabs(a[i]-a[j])がそのまま答えです
それ以外の場合、i < k < jのうちどれかをj番目まで動かした後、iをk番目まで動かせばいい(このkは既にj番目まで移動済みなのでj番目まで動かしていることになります)ので、
dp[i][k] + dp[k][j] (i < k < j)
が答えの候補です。更に、iをj-1番目まで動かす最善の手を打った後、iとjの差を計算する
dp[i][(j-1+N)%N] + abs(a[i]-a[j])
の動かし方も答えの候補です。これらの最大値を取ります。

  • ソース
import java.util.Arrays;
import java.util.Scanner;

//Gather on the Clock
public class AOJ2028 {

	int n;
	int[] a;
	int[][] dp;
	
	int get(int i, int j){
		if(i==j)return 0;
		if(dp[i][j]!=-1)return dp[i][j];
		if((i+1)%n==j)return dp[i][j]=Math.abs(a[i]-a[j]);
		int res = get(i, (j-1+n)%n) + Math.abs(a[i]-a[j]);
		int k = (i+1)%n;
		while(k!=j){
			res = Math.max(res, get(i, k)+get(k, j));
			k = (k+1)%n;
		}
		return dp[i][j] = res;
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		int T = sc.nextInt();
		while(T--!=0){
			n = sc.nextInt();
			a = new int[n];
			for(int i=0;i<n;i++)a[i]=sc.nextInt();
			dp = new int[n][n];
			for(int[]d:dp)Arrays.fill(d, -1);
			int res = 0;
			for(int i=0;i<n;i++)res=Math.max(res, get(i, (i-1+n)%n));
			System.out.println(res);
		}
	}
	
	public static void main(String[] args) {
		new AOJ2028().run();
	}
}