P1043 数字游戏
爆搜+剪枝 or 类似于区间dp的dp
先把题目中的条件转化成我们容易解决的。
首先当然是化环成链。做法就是开两倍的空间,然后枚举每一个点开始的情况。
(或者你用高深的膜法也可以啊)
这里提供两种方法,小菜鸡只会第一种。
法一:爆搜+剪枝
大体思路就是枚举现在已经切成了多少段,当切到\(m\)段了就停止结算答案。
不知道为什么,我们需要记录一个\(pos\)为上一次划分的终点,下一次枚举就从\(pos+1\)开始就可以了。
大概这么写一写再调试看一下就能够写出来一个爆搜的程序了。
ok,交上去。但是T掉了一个点,就是那个数据范围最大的点。
所以我们可以考虑剪枝了。
我们的硬性要求是求出全局的最大值和最小值,那么我们进行最优性剪枝。
剪枝方法:如果当前数大于等于当前最小值 并且 当前数乘以9的\(m-t+1\)次方还小于等于当前最大值时,剪枝!
所以就可以轻松地跑过这道题啦!
代码:
#include<cstdio>
#include<cmath>
#include<algorithm>
const int maxn = 55, maxm = 10;
const int INF = 0x3f3f3f3f;
int minv = INF, maxv = -INF;
int a[maxn * 2];
int n, m;
int turn(int x)
{
return (x % 10 + 10) % 10;
}
int pao(int x, int y)
{
int ans = 1;
for(int i = 1; i <= y; i++) ans *= x;
return ans;
}
void dfs(int t, int pos, int res, int start)
{
if(res * pao(9, m - t + 1) <= maxv && res >= minv) return;
if(t == m)
{
res *= turn(a[n + start - 1] - a[pos]);
minv = std::min(minv, res);
maxv = std::max(maxv, res);
return;
}
for(int i = pos + 1; i < start + n - m + t; i++)// 选当前区间的右端点(闭)
{
dfs(t + 1, i, res * turn(a[i] - a[pos]), start);
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i + n] = a[i];
}
for(int i = 1; i <= n + n; i++) a[i] += a[i - 1];
for(int i = 1; i <= n; i++) dfs(1, i - 1, 1, i);
printf("%d\n%d\n", minv, maxv);
return 0;
}
法二:类似于区间dp的dp
这道题其实跟“石子合并”是很像的,只是多了一维而已。
那么我们要处理的,就是一段区间的数字,分成若干段区间的最大最小值了。
设dp[i][j][l]
为\([i,j]\)这段区间内的数字被分为\(l\)段时的最大最小值。
那么可以有
\[dp[i][j][l]=dp[i][k][l - 1] \times turn(a_{k+1}+...+a_j)
\]
那一段区间和显然可以直接用前缀和咯。
然后就差不多是这个样子。注意初始化就可以了。
但是这道题还时反应了我的一个错误的思路:
这道题的划分个数显然是小于等于区间长度的,所以我们不能完全像区间dp地那样套模板。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
const int maxn = 110, maxm = 10;
const int INF = 0x3f3f3f3f;
int a[maxn];
ll dp1[maxn][maxn][maxm], dp2[maxn][maxn][maxm];// min and max
ll minv = INF, maxv = -INF;
int n, m;
int turn(int x)
{
return (x % 10 + 10) % 10;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i + n] = a[i];
}
for(int i = 1; i <= n + n; i++) a[i] += a[i - 1];
for(int i = 1; i <= n + n; i++)
{
for(int j = i; j <= n + n; j++)
{
dp1[i][j][1] = turn(a[j] - a[i - 1]);
dp2[i][j][1] = turn(a[j] - a[i - 1]);
}
}
for(int len = 2; len <= m; len++)
{
for(int i = 1; i <= n + n; i++)
{
for(int j = i + len - 1; j <= n + n; j++)
{
dp1[i][j][len] = INF;
}
}
}
for(int len = 2; len <= m; len++)
{
for(int i = 1; i <= n + n; i++)
{
for(int j = i + len - 1; j <= n + n; j++)
{
for(int k = i + len - 2; k < j; k++)
{
dp1[i][j][len] = std::min(dp1[i][j][len], dp1[i][k][len - 1] * turn(a[j] - a[k]));
dp2[i][j][len] = std::max(dp2[i][j][len], dp2[i][k][len - 1] * turn(a[j] - a[k]));
}
}
}
}
for(int i = 1; i <= n; i++)
{
minv = std::min(minv, dp1[i][i + n - 1][m]);
maxv = std::max(maxv, dp2[i][i + n - 1][m]);
}
printf("%lld\n%lld\n", minv, maxv);
return 0;
}