【C++题解】数字游戏

16 篇文章 0 订阅

题目链接: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][i1](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][i1](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;
}

写文章不易,望大家给 JG_DF_ 点个赞!

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值