noip2003普及组 数字游戏

题目

数字游戏

算法

(DP,区间DP) O ( n 3 m ) O(n^3m) O(n3m)

首先将环从起点处断开,然后复制一遍接在后面,这样原问题就转化成了线段型的模型。

然后从集合角度来分析状态表示和状态计算:

状态表示:

  • f[l][r][i],表示所有将 a[l…r] 分割成 i 部分的方案的最大乘积;
  • g[l][r][i] ,表示所有将 a[l…r] 分割成 i 部分的方案的最小乘积;

状态计算:

首先考虑 f[l][r][i] 如何计算,关键是寻找“集合划分的依据”,划分依据一般选取“最后一步的操作”,所以这里我们可以按最后一部分的位置来将 f[l][r][i] 所表示的所有方案划分成若干类:

  • 最后一段是 a[l + i - 1…r] 的所有划分方案;
  • 最后一段是 a[l + i…r] 的所有划分方案;
  • ···
  • 最后一段是 a[r…r] 的所有划分方案;

如果最后一段是 a[k…r] ,那么这一类的最大值是 f[l][k - 1][i - 1] * sum(a[k…r]) ,其中 sum() 计算某一段数的和模10的余数。

f[l][r][i] 取所有类别的最大值即可。
g[l][r][i] 的计算方式类似。

最终枚举所有长度是n的区间,取最大值/最小值即可。

时间复杂度

状态总共有 O ( n 2 m ) O(n^2m) O(n2m) 个,计算每个状态需要 O ( n ) O(n) O(n)的计算量,因此总时间复杂度是 O ( n 3 m ) O(n^3m) O(n3m)

C++ 代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110, M = 10, INF = 0x3f3f3f3f;

int n, m;
int w[N], s[N]; // w存每个数的值,s是前缀和
int f[N][N][M], g[N][N][M]; // f是最小值,g是最大值

// 求模时的余数
int get_mod(int x)
{
    return (x % 10 + 10) % 10;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> w[i]; // 每次先把w[i]读进来
        w[i + n] = w[i]; // w复制一倍放到后面去
    }
    for(int i = 1; i <= n * 2; i++)
        s[i] = s[i - 1] + w[i]; // 求前缀和

    // 初始化
    memset(f, 0x3f, sizeof f);
    memset(g, -0x3f, sizeof g);

    for(int len = 1; len <= n; len++)
        for(int l = 1; l + len - 1 <= n * 2; l++)
        {
            int r = l + len - 1; // 求出右端点
            f[l][r][1] = g[l][r][1] = get_mod(s[r] - s[l - 1]);
            for(int k = 2; k <= m; k++) // 枚举区间的长度
                for(int j = l + k - 2; j < r; j++)  // 枚举划分的点
                {
                    f[l][r][k] = min(f[l][r][k], f[l][j][k - 1] * get_mod(s[r] - s[j])); // 后面一部分是[j+1,r]
                    g[l][r][k] = max(g[l][r][k], g[l][j][k - 1] * get_mod(s[r] - s[j]));
                }
        }

    int maxv = -INF, minv = INF;
    for(int i = 1; i < n; i++)
    {
        maxv = max(maxv, g[i][i + n - 1][m]);
        minv = min(minv, f[i][i + n - 1][m]);
    }

    cout << minv << endl;
    cout << maxv << endl;

    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值