2010-1149

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

== Contest 14 by ACFun - Piano ==


'''题目大意'''[[BR]]
在钢琴的52个白键上用两只手弹琴,规定每只手都要占至少5个键,而一只手最多可以弹9个键。为了弹到所有的键,需要左手或者右手的移动,每次移动的消耗为大拇指移动距离的平方根取整。给定初始时两手大拇指所在的键位,以及所有要弹的键,求最小的总消耗。

'''解法'''[[BR]]
DP即可,dp的状态需要三维,分别为当前弹到了第几个键、左手大拇指所在位置、右手大拇指所在位置。实现中为了节省空间,第一维可以用滚动数组来实现。

'''代码如下(by t!__nt):'''
{{{
#!cpp
#include <cstdio>
#include <cstring>

const int N = 52;
const int MAXN = 60 + 1;
const int INF = 1012345678;

int SQRT[MAXN], dp[2][MAXN][MAXN], L, R, M;
bool valid[MAXN][MAXN];

inline int min(int a, int b) {
    return a < b ? a : b;
}

inline int max(int a, int b) {
    return a > b ? a : b;
}

inline int abs(int a) {
    return a < 0 ? -a : a;
}

int main() {
    for (int i = 1; i * i < MAXN; i++) SQRT[i * i] = i;
    for (int j = 2; j < MAXN; j++) {
        if (!SQRT[j]) SQRT[j] = SQRT[j - 1];
    }
    for (int i = 0; i < MAXN; i++) {
        for (int j = 0; j < MAXN; j++) {
            valid[i][j] = (4 <= i && i <= 51 && 0 <= j && j <= 47 && (i < j || abs(i - j) >= 9));
        }
    }
    while (scanf("%d%d%d", &L, &R, &M) != EOF) {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                dp[0][i][j] = INF;
            }
        }
        dp[0][L][R] = 0;
        int t1 = 0, t2;
        for (int i = 0; i < M; i++) {
            t2 = t1 ^ 1;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    dp[t2][i][j] = INF;
                }
            }
            int button;
            scanf("%d", &button);
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (dp[t1][i][j] == INF) continue;
                    // move left hand
                    if (i - 8 <= button && button <= i) {
                        dp[t2][i][j] = min(dp[t2][i][j], dp[t1][i][j]);
                    } else {
                        for (int ii = min(button + 8, 51); ii >= button; ii--) {
                            if (!valid[ii][j]) continue;
                            dp[t2][ii][j] = min(dp[t2][ii][j], dp[t1][i][j] + SQRT[abs(i - ii)]);
                        }
                    }
                    // move right hand
                    if (j <= button && button <= j + 8) {
                        dp[t2][i][j] = min(dp[t2][i][j], dp[t1][i][j]);
                    } else {
                        for (int jj = max(button - 8, 0); jj <= button; jj++) {
                            if (!valid[i][jj]) continue;
                            dp[t2][i][jj] = min(dp[t2][i][jj], dp[t1][i][j] + SQRT[abs(j - jj)]);
                        }
                    }
                }
            }
            t1 = t2;
        }
        int res = INF;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                res = min(res, dp[t1][i][j]);
            }
        }
        printf("%d\n", res);
    }
    return 0;
}
}}}

Contest 14 by ACFun - Piano

题目大意

在钢琴的52个白键上用两只手弹琴,规定每只手都要占至少5个键,而一只手最多可以弹9个键。为了弹到所有的键,需要左手或者右手的移动,每次移动的消耗为大拇指移动距离的平方根取整。给定初始时两手大拇指所在的键位,以及所有要弹的键,求最小的总消耗。

解法

DP即可,dp的状态需要三维,分别为当前弹到了第几个键、左手大拇指所在位置、右手大拇指所在位置。实现中为了节省空间,第一维可以用滚动数组来实现。

代码如下(by t!__nt):

#include <cstdio>
#include <cstring>
const int N = 52;
const int MAXN = 60 + 1;
const int INF = 1012345678;
int SQRT[MAXN], dp[2][MAXN][MAXN], L, R, M;
bool valid[MAXN][MAXN];
inline int min(int a, int b) {
    return a < b ? a : b;
}
inline int max(int a, int b) {
    return a > b ? a : b;
}
inline int abs(int a) {
    return a < 0 ? -a : a;
}
int main() {
    for (int i = 1; i * i < MAXN; i++) SQRT[i * i] = i;
    for (int j = 2; j < MAXN; j++) {
        if (!SQRT[j]) SQRT[j] = SQRT[j - 1];
    }
    for (int i = 0; i < MAXN; i++) {
        for (int j = 0; j < MAXN; j++) {
            valid[i][j] = (4 <= i && i <= 51 && 0 <= j && j <= 47 && (i < j || abs(i - j) >= 9));
        }
    }
    while (scanf("%d%d%d", &L, &R, &M) != EOF) {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                dp[0][i][j] = INF;
            }
        }
        dp[0][L][R] = 0;
        int t1 = 0, t2;
        for (int i = 0; i < M; i++) {
            t2 = t1 ^ 1;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    dp[t2][i][j] = INF;
                }
            }
            int button;
            scanf("%d", &button);
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (dp[t1][i][j] == INF) continue;
                    // move left hand
                    if (i - 8 <= button && button <= i) {
                        dp[t2][i][j] = min(dp[t2][i][j], dp[t1][i][j]);
                    } else {
                        for (int ii = min(button + 8, 51); ii >= button; ii--) {
                            if (!valid[ii][j]) continue;
                            dp[t2][ii][j] = min(dp[t2][ii][j], dp[t1][i][j] + SQRT[abs(i - ii)]);
                        }
                    }
                    // move right hand
                    if (j <= button && button <= j + 8) {
                        dp[t2][i][j] = min(dp[t2][i][j], dp[t1][i][j]);
                    } else {
                        for (int jj = max(button - 8, 0); jj <= button; jj++) {
                            if (!valid[i][jj]) continue;
                            dp[t2][i][jj] = min(dp[t2][i][jj], dp[t1][i][j] + SQRT[abs(j - jj)]);
                        }
                    }
                }
            }
            t1 = t2;
        }
        int res = INF;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                res = min(res, dp[t1][i][j]);
            }
        }
        printf("%d\n", res);
    }
    return 0;
}