top1编程
← 返回题目
题解

抽卡片

1 条题解

  • 0
    @ 2026-8-7 15:19:56

    P4910 抽卡片(提高)

    解题思路

    第一步,理解题意。 N张卡片摆成一行,每张卡片上写着一个正整数。游戏过程中,玩家每次从这一行里取出一张卡片,但不能取第一张和最后一张。取出一张卡片k的得分,等于k左边的卡片、k本身、k右边卡片三个数字相乘。目标是把中间所有卡片都取完,并且让总得分最少。

    第二步,发现区间的结构。 假设现在区间(i,j)里只留下了最左边的卡片i和最右边的卡片j,它们之间的卡片都已经被取走了。观察发现,最后被取出的那张卡片k一定在i和j之间,它被取走时i和j都还留在场上,所以这次得分就是a[i]×a[k]×a[j]。在取k之前,k左边的区间(i,k)和右边的区间(k,j)里的卡片必须都已经取完。

    第三步,设计动态规划。 定义dp[i][j]表示把区间(i,j)内的所有卡片取完需要的最小得分(i和j这两张不取)。相邻两张卡片之间没有可取的卡片,所以dp[i][i+1]=0。转移方程:dp[i][j]取min( dp[i][k] + dp[k][j] + a[i]*a[k]*a[j] ),其中k遍历i+1到j-1。

    第四步,按区间长度从小到大递推。 因为计算长区间要用到短区间,所以外层循环从小到大枚举区间长度len,内层枚举左端点i,右端点j=i+len。例如样例10 1 50 50 20 5,最终dp[0][5]=3650,与样例输出一致。

    第五步,注意细节。 数字范围1到100,三张相乘最大100万,总得分不会超过int的范围,但稳妥起见用long long存储。当N=3时,区间(0,2)里只有k=1这一张卡可取,答案就是a[0]*a[1]*a[2]。因为第一张和最后一张永远不能取,最终正好剩下这两张,答案就是dp[0][N-1]。

    参考代码

    // 抽卡片:区间DP,dp[i][j]表示区间(i,j)内卡片被取完的最小得分
    #include <iostream>
    using namespace std;
    long long a[105];
    long long dp[105][105];
    int main() {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++) cin >> a[i];
        // len为区间跨度,相邻两张卡片之间无卡片可取,dp[i][i+1]为0
        for (int len = 2; len < n; len++) {
            for (int i = 0; i + len < n; i++) {
                int j = i + len;
                long long best = 1e18;
                // 枚举最后一张取k,此时k的左右两边正是i和j
                for (int k = i + 1; k < j; k++) {
                    long long cur = dp[i][k] + dp[k][j] + a[i] * a[k] * a[j];
                    if (cur < best) best = cur;
                }
                dp[i][j] = best;
            }
        }
        cout << dp[0][n - 1] << endl;
        return 0;
    }
    

    复杂度分析

    一共有O(N²)个区间,每个区间要枚举O(N)个k,所以时间复杂度是O(N³)。N最大是100,100³等于100万次运算,完全可以承受。dp数组的大小是N×N,空间复杂度O(N²)。每张卡片的数字不超过100,三个数相乘最大100万,总得分用long long存放安全无溢出。

    • 1