Lilliput Steps

小さな一歩から着実に. 数学やプログラミングのことを書きます.

AOJ 2312 - Magical Girl Sayaka-chan

問題文 : 魔法少女さやかちゃん

解法 :
問題文で示されている円環が直線だと考えると, ある音符とある音符の間は、昇順になっていることが望ましい.
そこで、始点を音程のうちもっとも小さいもの, 終点を音程のうちもっとも大きいものとし, 始点->終点 にいく道を2つ作ることを考える. (これで円環が表現できる.)

どの道に割り振るかを全て確かめるとO(2^N)時間でこの問題がとけるが, ここでは
dp(a, b) -> 道Aの最後の音程がK[a], 道Bの最後の音程がK[b]
というdpテーブルをもち、メモ化再帰をしている.
こうすることで, O(N^2)時間でこの問題を解くことが出来る.



コード :

#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
typedef long long ll;
 
int N, M, L;
ll K[2048];
ll SSum[100001];
ll memo[2048][2048];
 
ll pls(int r, int l)
{
    return ((SSum[r] - SSum[l]) / (ll)L);
}
 
ll saveSayaka(int l, int r)
{
    int next = max(l, r) + 1;
     
    if (memo[l][r] >= 0){
        return (memo[l][r]);
    }
     
    if (next == N - 1){
        return (pls(K[next], K[l] - 1) + pls(K[next], K[r] - 1));
    }
     
    ll left = saveSayaka(next, r) + pls(K[next], K[l] - 1);
    ll right = saveSayaka(l, next) + pls(K[next], K[r] - 1);
     
    return (memo[l][r] = min(left, right));
}
 
int main(void)
{
    scanf("%d %d %d", &N, &M, &L);
     
    for (int i = 0; i < N; i++){
        scanf("%d", &K[i]);
    }
     
    for (int i = 0; i < M; i++){
        int s;
        scanf("%d", &s);
        SSum[i + 1] = SSum[i] + s;
    }
     
    sort(K, K + N);
    memset(memo, -1, sizeof(memo));
     
    printf("%lld\n", saveSayaka(0, 0));
}