插入乘号问题(DP)

一、问题提出

在一个由n个数字组成的数字串中插入r个乘号(1<=r<n<16),将它分成r+1个整数,找出一种乘号的插入方法,使得r+1个整数的乘积最大。

例如,对给定的数串847313926,如何插入r=5个乘号,使其积最大?

当给定乘号数量时首先考虑以枚举解题:

例如在7个数字之间插入三个乘号

#include<iostream>
#include<string>
using namespace std;
string str;
int s[10];
int t[10],father[10];
int main()
{
	int i,j,count=1;
	_int64 d,y,ans=0;
	cin>>str;
	int k=str.length();
	for(i=0;i<k;i++)
		s[i+1]=str[i]-'0';
	t[0]=0;t[4]=7;
	for(t[1]=1;t[1]<=4;t[1]++)
		for(t[2]=t[1]+1;t[2]<=5;t[2]++)
			for(t[3]=t[2]+1;t[3]<=6;t[3]++)
			{
				y=1;
				for(i=0;i<=3;i++)
				{
					d=0;
					for(j=t[i]+1;j<=t[i+1];j++)
						d=d*10+s[j];
					y*=d;
				}
				if(ans<y)
				{
					ans=y;
					for(i=1;i<4;i++)
						father[i]=t[i];
				}
			}
			printf("分别插入到%d    %d    %d之后!!!最大值为%lld\n",father[1],father[2],father[3],ans);

			return 0;
}

对于一般插入r个乘号,采用穷举已不适合。注意到插入r个乘号是一个多阶段决策问题,应用动态规划来求解是适宜的。

二、动态规划设计

1.建立递推关系

设f(i,k)表示在前i位数中插入k个乘号所得乘积的最大值,a(i,j)表示从第i个数字到第j个数字所组成的j-i+1(i<=j)位整数值。

为了寻求递推关系,先看一个实例:对给定的847313926,如何插入r=5个乘号,使其乘积最大?我们的目标是为了求取最优值f(9,5)。

①设前8个数字中已插入4个乘号,则最大乘积为f(8,4)*6;

②设前7个数字中已插入4个乘号,则最大乘积为f(7,4)*26;

③设前6个数字中已插入4个乘号,则最大乘积为f(6,4)*926;

④设前5个数字中已插入4个乘号,则最大乘积为f(5,4)*3926;

比较最大值即为f(9,5)。

依此类推,为了求f(8,4):

①设前7个数字中已插入3个乘号,则最大乘积为f(7,3)*2;

②设前6个数字中已插入3个乘号,则最大乘积为f(6,3)*92;

③设前5个数字中已插入3个乘号,则最大乘积为f(5,3)*392;

④设前4个数字中已插入3个乘号,则最大乘积为f(4,3)*1392;

比较以上4个数值的最大值即为f(8,4)。

一般地,为了求取f(i,k),考察数字串的前i个数字,设前j(k<=j<i)个数字中已插入k-1个乘号的基础上,在第j个数字后插入第t个乘号,显然此时的最大乘积为f(j,k-1)*a(j+1,i)。于是可以得递推关系式:

f(i,k)=max(f(j,k-1)*a(j+1, i))       (k<=j<i)

前j个数字没有插入乘号时的值显然为前j个数字组成的整数,因而得边界值为:

f(j,0)=a(1,j)        (1<=j<=i)

为简单计,在程序设计中省略a数组,用变量d替代。

2.递推计算最优值

for (d = 0, j = 1; j <= n; j++) {
	d = d * 10 + b[j - 1];	// 输入数字串每一位赋值给b数组
	f[j][0] = d;			// 计算初始值f[j][0]
}

for (k = 1; k <= r; k++)
{
	for (i = k + 1; i <= n; i++)
		for (j = k; j < i; j++) {
			for (d = 0, u = j + 1; u <= i; u++)
				d = d * 10 + b[u - 1];	// 计算d即为a(j+1,i)
			if (f[i][k] < f[j][k - 1] * d)	// 递推求取f[i][k]
				f[i][k] = f[j][k - 1] * d;
		}
}

3.构造最优解

为了能打印相应的插入乘号的乘积式,设置标注位置的数组t(k)与c(i,k),其中c(i,k)为相应的f(i,k)的第k个乘号的位置,而t(k)标明第k个乘号“*”的位置,例如,t(2)=3,表示第2个“*”号在第3个数字后面。

当给数组元素赋值f(i,k)=f(j,k-1)*d时,作相应赋值c(i,k)=j,表示f(i,k)的第k个乘号的位置是j。在求得f(n,r)的第r个乘号位置t(r)=c(n,r)=j的基础上,其他t(k)  (1<=k<=r-1)可应用下式逆推产生:

t(k)=c(t(k+1),k)

根据t数组的值,可直接按字符形式打印表面积所求得的插入乘号的乘积式。

// 在一个数中插入r个乘号,使其积最大
// 利用动态规划求解
#include <stdio.h>
#include <string.h>

int main()
{
	char numStr[16];
	int i, j, k, len, u, r, b[16], t[16], c[16][16];
	double f[17][17], d;

	printf("请输入整数:");
	scanf("%s", numStr);
	printf("请输入插入的乘号个数r: ");
	scanf("%d", &r);
	len = strlen(numStr);
	if (len <= r) {
		printf("输入的整数位够数不够或r太大!/n");
		return 0;
	}

	printf("在整数%s中插入%d个乘号,使乘积最大:/n", numStr, r);
	for (d = 0, j = 0; j <= len - 1; j++)
		b[j] = numStr[j] - '0';
	for (d = 0, j = 1; j <= len; j++) {
		d = d * 10 + b[j - 1];	// 把b数组的一个字符转化为数值
		f[j][0] = d;			// f[j][0]赋初始值
	}

	for (k = 1; k <= r; k++)
		for (i = k + 1; i <= len; i++)
			for (j = k; j < i; j++) {
				for (d = 0, u = j + 1; u <= i; u++)
					d = d * 10 + b[u - 1];
				if (f[i][k] < f[j][k - 1] * d) {
					f[i][k] = f[j][k - 1] * d;
					c[i][k] = j;
				}
			}

	t[r] = c[len][r];
	for (k = r - 1; k >= 1; k--)
		t[k] = c[t[k + 1]][k];		// 逆推出第k个*号的位置
	t[0] = 0; t[r + 1] = len;
	for (k = 1; k <= r + 1; k++) {
		for (u = t[k - 1] + 1; u <= t[k]; u++)
			putchar(numStr[u - 1]);	// 输出最优解
		if (k < r + 1)
			putchar('*');
	}
	printf("=%.0f/n", f[len][r]);		// 输出最优值

	return 0;
}

代码选自<<趣味编程100>>

部分内容来自http://blog.csdn.net/jqmczx/article/details/6403854

时间: 2024-10-27 05:08:01

插入乘号问题(DP)的相关文章

13- 整数划分插入乘号积最大(四)

/*                                            整数划分(四)时间限制:1000 ms  |  内存限制:65535 KB难度:3 描述 暑假来了,hrdv 又要留学校在参加ACM集训了,集训的生活非常Happy(ps:你懂得),可是他最近遇到了一个难题,让他百思不得其解,他非常郁闷..亲爱的你能帮帮他吗? 问题是我们经常见到的整数划分,给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积 输入    第

字符串中插入加号(dp)动态规划

题目:长度为m的字符串插入n个加号求最小和.例如string str="123456",n=2;输出12+34+56的和为102,同时输出2 4,也就是加号位置.下面为实现思路: 实现过程主要就是区间dp的思想,其中状态转移方程为dp[i][j] = min(dp[k][j - 1] + getnum(str.substr(k, i-k)), dp[i][j]);//其中dp[i][j]表示1-i中使用了j个加号,在这里我用struct主要就是记录加号的位置. #include<

HDU 2512 一卡通大冒险(dp)

一卡通大冒险 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2137    Accepted Submission(s): 1430 Problem Description 因为长期钻研算法, 无暇顾及个人问题,BUAA ACM/ICPC 训练小组的帅哥们大部分都是单身.某天,他们在机房商量一个绝妙的计划"一卡通大冒险".这个

hdu 2227 dp+树状数组优化

很容易可以想到状态转移方程: dp[i] = segma: dp[j] ( j < i && a[j] <= a[i] ), 其中dp[i]表示以a[i]结尾的不降子序列的个数. 但是n非常大,n^2的算法必然TLE,仔细想想其实式子右边是一个区间和的形式,即小于等于a[i]的a[j]的dp[j]的和,所以可以将a[i]离散化成t后将dp[i]的值存在t位置上,每次求解dp[i]就是查询一个前缀和,可以用树状数组来维护. 举个例子:对于1,50,13,34这四个数,离散化后变成

生信-序列比较dp[未完成]

来自:生物信息学-陈铭第二版的一个例题. 题目: 目前的代码,运行不正确,关键就是不知道怎么回溯啊,回溯怎么标记呢? #include <iostream> #include<vector> using namespace std; vector<char> s1,t1;//在回溯的时候使用 string s,t;//输入两个字符串 int dp[30][30]; int maxs(int x,int y,int z){ if(x>=y&&x>

LeetCode dp专题

longest valid parentheses: dp[i]表示到i为止合法的()长度 s[i] == ')' : dp[i] = dp[i-2] + 2                          ( s[i]=='(' ) dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2]  ( s[i-1] == ')' && s[i-1-dp[i-1]] == '(' ) 注意判断数组下标值是否存在 72. Edit Distance 将word1转换成word2

题解:2018级算法第四次上机 C4-最小乘法

题目描述: 样例: 实现解释: 和字符串处理结合的动态规划,个人认为比较难分析出状态转移方程,虽然懂了之后挺好理解的 知识点: 动态规划,字符串转数字 题目分析: 首先按照最基础:依据题意设计原始dp数组,这里根据描可知有三个数需要考虑:数字串开始,数字串结尾和之间插入的乘号数量,因此基础dp[i][j][k],分别为开始,结束脚标和乘号数. 然后推导:考虑到添加乘号,为了使状态转移方程简单,最后固定位置,因此可以考虑每次都在最后插入乘号,插入乘号的位置便可倒序确定.此时数字串的开始位置便可固定

动态规划提醒总结

一.划分型dp 简介:简单来说就是需要把一个东西划分为m份,考虑如何划分最优. 例题: noip2000 乘积最大 题目描述  Description     今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰90周年.在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友XZ也有幸得以参加.活动中,主持人给所有参加活动的选手出了这样一道题目: 设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这

nyist oj 214 单调递增子序列(二) (动态规划经典)

单调递增子序列(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 给定一整型数列{a1,a2...,an}(0<n<=100000),找出单调递增最长子序列,并求出其长度. 如:1 9 10 5 11 2 13的最长单调递增子序列是1 9 10 11 13,长度为5. 输入 有多组测试数据(<=7) 每组测试数据的第一行是一个整数n表示序列中共有n个整数,随后的下一行里有n个整数,表示数列中的所有元素.每个整形数中间用空格间隔开(0<n<=1