数字游戏 题解
2023-08-30 23:15:50
发布于:广东
19阅读
0回复
0点赞
算法
(DP,区间DP)
首先将环从起点处断开,然后复制一遍接在后面,这样原问题就转化成了线段型的模型。
然后从集合角度来分析状态表示和状态计算:
状态表示:
- 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的区间,取最大值/最小值即可。
时间复杂度
状态总共有计算每个状态需要的计算量,因此总时间复杂度是
AC代码
#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[NULL][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;
}
这里空空如也
有帮助,赞一个