题目链接:https://gmoj.net/junior/#main/show/1130
题目描述
丁丁最近沉迷于一个数字游戏之中。这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易。游戏是这样的,在你面前有一圈整数(一共n个),你要按顺序将其分为m个部分,各部分内的数字相加,相加所得的m个结果对10取模后再相乘,最终得到一个数k。游戏的要求是使你所得的k最大或者最小。
例如,对于下面这圈数字(n=4,m=2):
当要求最小值时,((2-1) mod 10)×((4+3) mod 10)=1×7=7,要求最大值时,为((2+4+3) mod 10)×(-1 mod 10)=9×9=81。特别值得注意的是,无论是负数还是正数,对10取模的结果均为非负值。
丁丁请你编写程序帮他赢得这个游戏。
输入
输入文件第一行有两个整数,n(1≤n≤50)和m(1≤m≤9)。以下n行每行有个整数,其绝对值不大于104,按顺序给出圈中的数字,首尾相接。
输出
输出文件有两行,各包含一个非负整数。第一行是你程序得到的最小值,第二行是最大值。
样例输入
4 2
4
3
-1
2
样例输出
7
81
数据范围限制
做法
一道区间 dp 题。
因为题面给出的是一个环,我们可以试着破环为链,即用一个数组 a a a 模拟一个环。
然后我们给数组 a a a 做预处理前缀和,然后求出题目要求的最大值和最小值。
我们设 f [ l ] [ r ] [ i ] f[l][r][i] f[l][r][i] 表示在 l l l 到 r r r 的区间内总共能被分成 i i i 段的最大积。那么我们枚举 i , l , r i,l,r i,l,r ,然后枚举区间的分割段 l l l ~ r r r ,用 k k k 来存该分割段。
通过上面的解释,所得的动态转移方程为 f [ l ] [ r ] [ i ] = m a x ( f [ l ] [ r ] [ i ] , f [ l ] [ k ] [ i − 1 ] ∗ ( a [ r ] − a [ k ] ) ) f[l][r][i]=max(f[l][r][i],f[l][k][i-1]*(a[r]-a[k])) f[l][r][i]=max(f[l][r][i],f[l][k][i−1]∗(a[r]−a[k])) 。
同样的,我们又设 g [ l ] [ r ] [ i ] g[l][r][i] g[l][r][i] 表示在 l l l 到 r r r 的区间内总共能被分成 i i i 段的最小积。那么我们枚举 i , l , r i,l,r i,l,r ,然后枚举区间的分割段 l l l ~ r r r ,用 k k k 来存该分割段。
则所得的动态转移方程为 g [ l ] [ r ] [ i ] = m i n ( g [ l ] [ r ] [ i ] , g [ l ] [ k ] [ i − 1 ] ∗ ( a [ r ] − a [ k ] ) ) g[l][r][i]=min(g[l][r][i],g[l][k][i-1]*(a[r]-a[k])) g[l][r][i]=min(g[l][r][i],g[l][k][i−1]∗(a[r]−a[k])) 。
最后我们从 1 1 1 到 n n n 找出长度为 n n n 的区间的最大值和最小值并输出即可。
#include<cstdio>
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
int f[105][105][15],g[105][105][15];
int n,m,a[105],mn=1e9,mx;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i+n]=a[i];//破环为链,即用一个数组a模拟一个环
}
for(int i=2;i<=2*n;i++) a[i]+=a[i-1];//给数组a做预处理前缀和
for(int i=1;i<=2*n;i++)//给数组f和数组g做初始化
for(int j=i;j<=2*n;j++) f[i][j][1]=g[i][j][1]=((a[j]-a[i-1])%10+10)%10;
for(int i=2;i<=m;i++)//枚举被分成i段
for(int l=1;l<=2*n;l++)//枚举从l开始的区间
for(int r=l+1;r<=2*n;r++)//枚举到r结束的区间
{
g[l][r][i]=1e9;
for(int k=l+i-2;k<r;k++)//枚举区间的分割段l-r,用k来存该分割段。
{
f[l][r][i]=max(f[l][r][i],f[l][k][i-1]*(((a[r]-a[k])%10+10)%10));
//f[l][r][i]表示在l到r的区间内总共能被分成i段的最大积
g[l][r][i]=min(g[l][r][i],g[l][k][i-1]*(((a[r]-a[k])%10+10)%10));
//g[l][r][i]表示在l到r的区间内总共能被分成i段的最小积
}
}
for(int i=1;i<=n;i++)//从1到n找出长度为n的区间的最大值和最小值
{
mx=max(mx,f[i][i+n-1][m]);
mn=min(mn,g[i][i+n-1][m]);
}
printf("%d\n%d",mn,mx);
return 0;
}