洛谷 P1018 [NOIP2000 提高组] 乘积最大

题目描述:

传送门

今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰 90 周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友 XZ 也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目:

设有一个长度为 N 的数字串,要求选手使用 K 个乘号将它分成 K+1 个部分,找出一种分法,使得这 K+1 个部分的乘积能够为最大。

同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:

有一个数字串:312,当N=3,K=1 时会有以下两种分法:

  1. 3×12=36
  2. 31×2=62

这时,符合题目要求的结果是:31×2=62。

现在,请你帮助你的好朋友 XZ 设计一个程序,求得正确的答案。

输入格式

程序的输入共有两行:

第一行共有 2个自然数 N,K。

第二行是一个长度为 N 的数字串。

输出格式

结果显示在屏幕上,相对于输入,应输出所求得的最大乘积(一个自然数)。

输入输出样例

输入 #1

4 2
1231

 输出 #1

62

数据范围与约定

对于 60%的测试数据满足 6≤N≤20。
对于所有测试数据,6≤N≤40,1≤K≤6。

题意: 

        题目给出位数为N的整数,现要将该整数“切开”K刀,将K+1个部分(位数>=1)组成整数后相乘,求乘积最大值。

思路:

        维护数组dp[i][j],记作前i个数字(左边开始)“切开”j刀的乘积最大值;并开辟数组mp[i][j],记作第i个数字到第j个数字(左边开始)组成的整数;那么对应的转移方程为dp[i][j] = max(dp[i][j], dp[k][j-1]*mp[k+1][j]), k为枚举最后一个因数的起始前一个位置,k=[j,i);最后还因为输入的整数过大,加上高精运算优化一下即可。

 dp

        我觉得只要能理解这个转移方程这道题就大差不差了,高精只不过时计算数据的工具,我先来解释一下转移方程:dp[i][j] = max(dp[i][j], dp[k-1][j-1]*mp[k][j])

        对于dp[i][j],即前i个数(包括i)被切j刀,由于我们时从i=0,j=0遍历过来的,即任意i0(i0<i), 对于的dp[i0][j-1]都是已经计算好的,所以我们只要去枚举最后一个乘法的位置,换句话说就是去枚举最后一个因数last,那么乘积就是dp[i0][j-1]*mp[i0+1][i],在枚举所以最后一个因数last后取最大的dp[i0][j-1]*mp[i0+1][i]就是我们要求的dp[i][j];

        上图是以dp[6][3]为例,其中dp[1][2],dp[2][2]无意义,因为“切的刀数”大于等于数的位数;我们在遍历枚举的时候可以将其continue掉;

        首先是先初始化mp数组:

// 获取(左边)第i位到第j位组合的整数值; 
int get_nums(int i, int j){
	if(i == j) return num[i];
	int ret = 0;
	for(;i <= j;i++) ret = ret * 10 + num[i];
	return ret;
} 

// mp;
for(int i = 1;i <= N;i++){
	for(int j = i; j <= N;j++){
		mp[i][j] = get_nums(i, j);
	}
}

        再维护dp数组,其中k变量即为上图中i0,为了每次枚举有意义,故从j处开始遍历枚举(解释一下为什么从j开始遍历,因为你要dp有意义,那么k要大于“刀数”j-1,所以从j开始):

// dp;
memset(dp, 0, sizeof(dp));
for(int i = 1;i <= N;i++){ 
	for(int j = 0;j <= 6;j++){ // 乘法个数j; 
		if(i <= j) break; // 乘法个数不能>=因数; 
		if(j == 0) {dp[i][j] = mp[1][i]; continue;}
		for(int k = j;k < i;k++){
			int last = mp[k+1][i]; // 最后一个因数; 
			dp[i][j] = max(dp[i][j], dp[k][j-1]*last);
		}
	}
}

        到这里去提交代码就有60分了,还差40分就是因为整数太大爆了,就算是long long也接不住10^39这么大的数,所以这里就要用高精度运算去解决这个问题,下面先给出完整的无高精dp做法源码:

60分源码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
#define endl '\n'

int N, K;
int dp[50][10] = {0}, mp[50][50], num[50]; // dp[i][j] 在第1位(右边)到第i位k=j时的乘积最大值;

// 获取(左边)第i位到第j位组合的整数值; 
int get_nums(int i, int j){
	if(i == j) return num[i];
	int ret = 0;
	for(;i <= j;i++) ret = ret * 10 + num[i];
	return ret;
} 

// 初始化; 
void Init(){
	cin >> N >> K;
	// num; 
	for(int i = 1;i <= N;i++){
		char ch; cin >> ch;
		num[i] = ch - '0';
	}
	// mp;
	for(int i = 1;i <= N;i++){
		for(int j = i; j <= N;j++){
			mp[i][j] = get_nums(i, j);
		}
	}
	// dp;
	memset(dp, 0, sizeof(dp));
	for(int i = 1;i <= N;i++){ 
		for(int j = 0;j <= 6;j++){ // 乘法个数j; 
			if(i <= j) break; // 乘法个数不能>=因数; 
			if(j == 0) {dp[i][j] = mp[1][i]; continue;}
			for(int k = j;k < i;k++){
				int last = mp[k+1][i]; // 最后一个因数; 
				dp[i][j] = max(dp[i][j], dp[k][j-1]*last);
			}
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	Init();
	cout << dp[N][K];
	
	return 0;
}

高精+dp

        高精度运算其实就是把int,double等这些数据类型用字符串类型string代替,再模拟加减乘除来解决int,double等类型精度不够的问题;所以这里需要将dp和mp的数据类型int转为string类型,再模拟乘法运算即可解决数据过大的问题。

        首先是对mp数组初始化的修改:

string mp[50][50], dp[50][50]; // int -> string;

 获取(左边)第i位到第j位组合的整数值; 
//int get_nums(int i, int j){
//	if(i == j) return num[i];
//	int ret = 0;
//	for(;i <= j;i++) ret = ret * 10 + num[i];
//	return ret;
//} 

// 获取(左边)第i位到第j位组合的整数值; 
string get_nums(int i, int j){
	string ret = "";
	if(i == j) return ret = num[i];
	for(;i <= j;i++) ret += num[i];
	return ret;
}

        然后就是修改dp过程,这里就要用到高精度乘法(其实就是字符串“10”和“11”模拟乘法最后求得字符串“110”的过程):

// 返回较大值;
string maxn(string a, string b){
	if(a.length() != b.length()) return a.length() > b.length() ? a : b;
	return a > b ? a : b;
} 

// 高精度乘法;
string mul(string a, string b){
	// string -> int;
	int len1 = a.length(), len2 = b.length(), len3 = 0;
	int a1[1010], b1[1010], c1[1010]; // 将string a,b的每一位数用int数组保存,用于模拟计算;
	memset(a1, 0, sizeof(a1));
	memset(b1, 0, sizeof(b1));
	memset(c1, 0, sizeof(c1));
	for(int i = 1;i <= len1;i++) a1[i] = a[len1-i] - '0'; // 为了进位方便,将整数倒转过来;
	for(int i = 1;i <= len2;i++) b1[i] = b[len2-i] - '0';
	// 相乘;
	for(int i = 1;i <= len2;i++){ // 取一位乘数;
		int in = 0;
		for(int j = 1;j <= len1;j++){ // 取一位被乘数; 
			c1[i+j-1] += a1[j] * b1[i] + in; // 第i位与第j位相乘是保存在i+j-1为上,模拟一下小学乘法计算就可以发现规律;
			in = c1[i+j-1] / 10; // 更新进位;
			c1[i+j-1] %= 10; // 剩余; 
		} 
		c1[i+len1] = in; // 处理最后进位; 
	} 
	len3 = len2 + len1;
	while(c1[len3] == 0 && len3 > 1) len3--; // 取消前置0且保证结果为0时保留一个0;
	// int -> string;
	string c = "";
	for(int i = len3;i >= 1;i--) c += '0' + c1[i];
	return c; 
} 

        下面就是dp过程的改动:

 dp;
//memset(dp, 0, sizeof(dp));
//for(int i = 1;i <= N;i++){ 
//	for(int j = 0;j <= 6;j++){ // 乘法个数j; 
//		if(i <= j) break; // 乘法个数不能>=因数; 
//		if(j == 0) {dp[i][j] = mp[1][i]; continue;}
//		for(int k = j;k < i;k++){
//			int last = mp[k+1][i]; // 最后一个因数; 
//			dp[i][j] = max(dp[i][j], dp[k][j-1]*last);
//		}
//	}
//}

// dp;
for(int i = 1;i <= N+10;i++) for(int j = 1;j <= 6+10;j++) dp[i][j] = "0";
for(int i = 1;i <= N;i++){ 
	for(int j = 0;j <= 6;j++){ // 乘法个数j; 
		if(i <= j) break; // 乘法个数不能>=因数; 
		if(j == 0) {dp[i][j] = mp[1][i]; continue;}
		for(int k = j;k < i;k++){
			string last = mp[k+1][i]; // 最后一个因数; 
			dp[i][j] = maxn(dp[i][j], mul(dp[k][j-1], last));
		}
	}
}

         好的到这里我们就实现了高精度优化dp了,下面给出AC源码:

AC源码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
#define endl '\n'

int N, K;
string num;
string mp[50][50], dp[50][50];

// 获取(左边)第i位到第j位组合的整数值; 
string get_nums(int i, int j){
	string ret = "";
	if(i == j) return ret = num[i];
	for(;i <= j;i++) ret += num[i];
	return ret;
}


// 返回较大值;
string maxn(string a, string b){
	if(a.length() != b.length()) return a.length() > b.length() ? a : b;
	return a > b ? a : b;
} 

// 高精度乘法;
string mul(string a, string b){
	// string -> int;
	int len1 = a.length(), len2 = b.length(), len3 = 0;
	int a1[1010], b1[1010], c1[1010];
	memset(a1, 0, sizeof(a1));
	memset(b1, 0, sizeof(b1));
	memset(c1, 0, sizeof(c1));
	for(int i = 1;i <= len1;i++) a1[i] = a[len1-i] - '0'; // 为了进位方便,将整数倒转过来;
	for(int i = 1;i <= len2;i++) b1[i] = b[len2-i] - '0';
	// 相乘;
	for(int i = 1;i <= len2;i++){ // 取一位乘数;
		int in = 0;
		for(int j = 1;j <= len1;j++){ // 取一位被乘数; 
			c1[i+j-1] += a1[j] * b1[i] + in;
			in = c1[i+j-1] / 10; // 更新进位;
			c1[i+j-1] %= 10; // 剩余; 
		} 
		c1[i+len1] = in; // 处理最后进位; 
	} 
	len3 = len2 + len1;
	while(c1[len3] == 0 && len3 > 1) len3--; // 取消前置0且保证结果为0时保留一个0;
	// int -> string;
	string c = "";
	for(int i = len3;i >= 1;i--) c += '0' + c1[i];
	return c; 
} 

void Init(){
	cin >> N >> K >> num;
	// mp;
	for(int i = 1;i <= N;i++){
		for(int j = i; j <= N;j++){
			mp[i][j] = get_nums(i-1, j-1);
		}
	}
	// dp;
	for(int i = 1;i <= N+10;i++) for(int j = 1;j <= 6+10;j++) dp[i][j] = "0";
	for(int i = 1;i <= N;i++){ 
		for(int j = 0;j <= 6;j++){ // 乘法个数j; 
			if(i <= j) break; // 乘法个数不能>=因数; 
			if(j == 0) {dp[i][j] = mp[1][i]; continue;}
			for(int k = j;k < i;k++){
				string last = mp[k+1][i]; // 最后一个因数; 
				dp[i][j] = maxn(dp[i][j], mul(dp[k][j-1], last));
			}
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	Init();
	cout << dp[N][K];
	
	return 0;
}

        好了这道题就到这啦,感谢观看到这里 ~\(≧▽≦)/~,还得去肝高精除法了\( ̄ー ̄) /,各位大佬看得高兴就给caikun点赞鼓励一下吧(•̀⌄•́)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值