AOJ2143 Adaptive Time Slicing Quantization
問題リンク Adaptive Time Slicing Quantization
- 概要
N個の[0.0, 1.0]の実数値列と整数M, Lが与えられる。
実数値列をM個の区間に分割する。ただしどの区間も最低2つ以上の要素を持たなければならない。更に、各区間毎に以下のようにして二乗誤差を計算する
・区間の最小値minと最大値maxを調べる
・区間の各要素aを
Qi = min + (i-1)*(max-min)/(2^L-1) : (1 <= i <= 2^L)
の内で最も近いものに量子化し、量子化された値と本来の数値との二乗誤差の総和をとる
実数値列をM個の区間に分割したとき、各区間の二乗誤差の総和の最小値を求めよ。
2 <= N <= 256
1 <= M <= M/2
1 <= L <= 8
- 解法
DPもといメモ化探索です。
dp[i][j]: 数列のi番目以降を使ってj個の区間を作った時の最小値
という表を埋めていきながら探索します。
dp[i][j]を埋めるために、区間を決めるループO(N)と、量子化する値を決めるループO(2^L)が必要になるので、計算量がざっとO(N^2*M*2^L)かかり大分危険です。量子化する値を2分探索を使って求めればO(2^L)がO(log 2^L)=O(L)になるのでO(N^2*M*L)程度に落とせます。これでTLE 16000msに対して14080msで通りました。ギリギリセーフです。
- ソース
import java.util.Arrays; import java.util.Scanner; //Adaptive Time Slicing Quantization public class AOJ2143 { int n, M, L, INF = 1<<29; double res; double[] a; double[][] dp; double bs(double min, double max, double x){ int l = 1, r = 1<<L; while(r-l>1){ int m = (l+r)/2; double v = min+(m-1)*(max-min)/((1<<L)-1); if(v<x)l=m; else r=m; } double lv = min+(l-1)*(max-min)/((1<<L)-1), rv = min+(r-1)*(max-min)/((1<<L)-1); return Math.abs(x-lv)<Math.abs(x-rv)?lv:rv; } double get(int k, int m){ if(n==k){ return m==0?0:INF; } if(m<0)return INF; if(dp[k][m]!=-1)return dp[k][m]; if(n<=k+1)return INF; double min = a[k], max = a[k], res = INF; for(int i=k+1;i<n;i++){ min = Math.min(min, a[i]); max = Math.max(max, a[i]); if(n<i+1+2*(m-1))break; double e = 0; for(int j=k;j<=i;j++){ double s = bs(min, max, a[j]); e += (s-a[j])*(s-a[j]); } res = Math.min(res, e+get(i+1, m-1)); } return dp[k][m] = res; } void run(){ Scanner sc = new Scanner(System.in); for(;;){ n = sc.nextInt(); M = sc.nextInt(); L = sc.nextInt(); if((n|M|L)==0)break; a = new double[n]; for(int i=0;i<n;i++)a[i]=sc.nextDouble(); dp = new double[n][M+1]; for(double[]d:dp)Arrays.fill(d, -1); System.out.printf("%.8f\n", get(0, M)); } } public static void main(String[] args) { new AOJ2143().run(); } }