#pragma GCC optimize("O3") #pragma GCC optimize("unroll-loops") #include #include #include #include using namespace std; // 巨大な数 (無限大として扱う) const long long INF = 1e18; // グローバル変数として入力を保持 int M, N; vector A, B; vector ans; // 高速なスライド最小値のための手動リングバッファ (std::dequeは遅いため) struct FastDeque { vector q_idx; vector q_val; int head, tail; FastDeque(int size) { q_idx.resize(size); q_val.resize(size); head = 0; tail = 0; } inline void clear() { head = 0; tail = 0; } inline bool empty() { return head == tail; } inline void push_back(int idx, long long val) { q_idx[tail] = idx; q_val[tail] = val; tail++; } inline void pop_back() { tail--; } inline void pop_front() { head++; } inline long long back_val() { return q_val[tail - 1]; } inline int front_idx() { return q_idx[head]; } inline long long front_val() { return q_val[head]; } }; // 容量 k を固定したときの最小コストを計算する関数 // 計算量: O(MN) long long calc(int k) { if (k <= 0) return INF; if (k > M) k = M; // dp[i] : Aの先頭i個をマッチングさせた最小コスト // メモリ節約のため1次元配列を使い回す (prev -> curr) vector dp(M + 1, INF); dp[0] = 0; // DP遷移用バッファ vector next_dp(M + 1); // Dequeの初期化 static FastDeque dq(M + 5); // Bの各要素についてループ (O(N)) for (int j = 0; j < N; ++j) { long long b_val = B[j]; dq.clear(); // Aとの距離の累積和 long long current_S = 0; // Aの各要素についてスライド最小値 (O(M)) for (int x = 0; x <= M; ++x) { // S[x] の更新: sum(|A[p] - b_val|) long long dist = (x > 0) ? abs(A[x-1] - b_val) : 0; current_S += dist; // 遷移式: next_dp[x] = S[x] + min(dp[y] - S[y]) // 範囲: x - k <= y <= x // 1. 現在の dp[x] - S[x] を Deque に追加 (最小値を維持) if (dp[x] != INF) { long long val = dp[x] - current_S; while (!dq.empty() && dq.back_val() >= val) { dq.pop_back(); } dq.push_back(x, val); } // 2. ウィンドウ範囲外 (index < x - k) を削除 if (!dq.empty() && dq.front_idx() < x - k) { dq.pop_front(); } // 3. 最小値を使って next_dp[x] を更新 if (!dq.empty()) { next_dp[x] = current_S + dq.front_val(); } else { next_dp[x] = INF; } } // 配列の更新 dp = next_dp; } return dp[M]; } // 分割統治による解の埋め込み // 範囲 [l, r] の答えを計算する。ans[l], ans[r] は既に計算済みとする。 void solve_dc(int l, int r) { if (l + 1 >= r) return; int mid = (l + r) / 2; // 中点の値を計算 ans[mid] = calc(mid); // 凸性チェック: (l, mid, r) が一直線上にあるか? // (y2 - y1) * (x3 - x2) == (y3 - y2) * (x2 - x1) // 注意: オーバーフローに気をつけるが、制約上 long long で足りる long long dy1 = ans[mid] - ans[l]; long long dx1 = mid - l; long long dy2 = ans[r] - ans[mid]; long long dx2 = r - mid; if (dy1 * dx2 == dy2 * dx1) { // 一直線上にある場合、間の値も線形補間できるため再帰しない // 直線の傾き long long slope = dy1 / dx1; // 割り切れるはず for (int i = l + 1; i < r; ++i) { if (i == mid) continue; ans[i] = ans[l] + slope * (long long)(i - l); } } else { // 直線でない場合は分割して再帰 solve_dc(l, mid); solve_dc(mid, r); } } int main() { // 入出力高速化 ios_base::sync_with_stdio(false); cin.tie(NULL); if (!(cin >> M >> N)) return 0; A.resize(M); for (int i = 0; i < M; ++i) cin >> A[i]; B.resize(N); for (int i = 0; i < N; ++i) cin >> B[i]; // ソート (必須) sort(A.begin(), A.end()); sort(B.begin(), B.end()); ans.resize(M + 1); // 両端の計算 ans[1] = calc(1); ans[M] = calc(M); // 分割統治開始 solve_dc(1, M); // 出力 for (int i = 1; i <= M; ++i) { cout << ans[i] << "\n"; } return 0; }