乘积最大(dfs+思维)

在这里插入图片描述
在这里插入图片描述
这道题以前我也遇见过,但是没搞明白,现在搞明白了:
这道题我当时一读题就想用dfs,但是有个问题就是我怎么把n-1个空位插k个乘号进去,并且最终判断最大值,因为dfs是要对每个位置都要枚举,并且前面的位置要占满;所以我就一直在想这个问题,结果没想出来,之后百度了一下,看了其他人的dfs,我感觉,那些人是真的大佬,我好菜。最后我还是看明白了,代码中count表示填的乘号的个数,res表示上几次段的数的乘积,index表示当前插入的下标,可以这样理解:
有了这个图才好理解下面说的话:
在这里插入图片描述
如果我要插入多个乘号,那么第一个乘号的范围就是[1,n-1],然后第二个乘号的范围就应该是[1,n],但是因为第二个的下限要受到第一个的制约,所以他本来的下限就应该是第一个的index+1;然后多个也照样这样理解;
现在的问题就是如何去把空位里面找出所有可能的填上k个乘号的填法,并且让每种填法比较;这里就需要知道这个问题:
因为dfs是对无间隙的遍历,且有一定范围;所以可以不难知道:每次遍历的范围是在[index+1,n-k+count];这里可以用笔算一下
这样每次dfs的时候只需要把前面的范围的值算出来之后传递给后面的dfs就可以解决这个问题了;如果仔细用脑袋想一下,也应该是这样的(把每次放置的位置所隔开的数都算出来,然后dfs(这个想法我挺佩服那些大佬的));
所以按照这种思路dfs就OK了:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll book[1000];
ll n,k,Max;
string s;
ll a[100];
void dfs(ll count,ll res,ll index){
	if(count==k){//当填够k个的时候
		ll num=0;
		  for(int i=index+1;i<=n;i++){//计算余下的数(如果越界就表示没有)
		  	  num=num*10+a[i];
		  }
		  Max=max(Max,res*num);
		  return ;
	}
	for(int i=index+1;i<=n-k+count;i++){//这里就是上面说的每个能走到的最大下标(当然如果越界了就不会计算下面的了)
		ll num=0;
		   for(int j=index+1;j<=i;j++){//这里就是把分段的可能性都计算(一个dfs一个可能性)
		   	num=num*10+a[j];
		   }
		   dfs(count+1,res*num,i);//这里是把上回的分段结果乘上res传给下一个dfs
	}
}
int main(){
	scanf("%lld %lld",&n,&k);
	cin>>s;
	for(int i=0;i<s.length();i++) a[i+1]=s[i]-'0';//这里初始化下标是从1开始的!!!
	dfs(0,1,0);//表示还没有填res=1是为了后面的计算
	printf("%lld\n",Max);
	return 0;
} 
<think>我们逐一分析每个题目的考点和可能的解法思路。由于题目数量较多,我们将对每个题目进行简要分析,并给出关键代码示例(如果需要)和解释。 1. **数组求和并判断和阈值大小** - 考点:基础数组遍历、条件判断 - 思路:遍历数组累加求和,然后与阈值比较。 - 代码示例: ```cpp #include <vector> using namespace std; string checkSumThreshold(vector<int>& nums, int threshold) { int sum = 0; for (int num : nums) { sum += num; } if (sum > threshold) { return "greater"; } else if (sum < threshold) { return "less"; } else { return "equal"; } } ``` 2. **根据两个字符串判断不同字符个数** - 考点:字符串遍历、字符比较 - 思路:同时遍历两个字符串,比较相同位置上的字符是否相同。 - 代码示例: ```cpp #include <string> using namespace std; int countDiffer(string s1, string s2) { int count = 0; // 假设两个字符串长度相同 for (int i = 0; i < s1.size(); i++) { if (s1[i] != s2[i]) { count++; } } return count; } ``` 3. **手写CNN池化,定义池化操作为取第二大的数** - 考点:二维数组操作、排序或部分排序 - 思路:在给定的池化窗口内,找到第二大的数。可以使用排序或部分排序(如使用nth_element)。 - 代码示例(假设池化窗口为2x2): ```cpp #include <vector> #include <algorithm> using namespace std; vector<vector<int>> poolSecondMax(vector<vector<int>>& input) { int rows = input.size(); int cols = input[0].size(); // 输出矩阵的行数和列数(假设步长为2,且窗口2x2) vector<vector<int>> output(rows/2, vector<int>(cols/2)); for (int i = 0; i < rows; i += 2) { for (int j = 0; j < cols; j += 2) { // 提取窗口内的4个元素 vector<int> window; window.push_back(input[i][j]); window.push_back(input[i][j+1]); window.push_back(input[i+1][j]); window.push_back(input[i+1][j+1]); // 部分排序:将第二大的元素放在倒数第二个位置 nth_element(window.begin(), window.begin()+1, window.end(), greater<int>()); output[i/2][j/2] = window[1]; // 取第二大的数 } } return output; } ``` 4. **求几何分布期望,要求浮点数输出,且只保留一位小数** - 考点:概率论(几何分布)、浮点数输出控制 - 思路:几何分布的期望为1/p(成功概率为p),然后按格式输出。 - 代码示例: ```cpp #include <iostream> #include <iomanip> using namespace std; int main() { double p; cin >> p; double expect = 1.0 / p; cout << fixed << setprecision(1) << expect << endl; return 0; } ``` 5. **求数组操作后数组和最小的结果** - 考点:数学思维、贪心(类似辗转相除法求最大公约数) - 思路:注意到操作的本质是辗转相减,最终整个数组会变成所有元素的最大公约数(多次操作后,每个数都变成gcd)。因此,最小和就是n倍的gcd(n为数组长度)。 - 代码示例: ```cpp #include <vector> #include <algorithm> using namespace std; int gcd(int a, int b) { if (b == 0) return a; return gcd(b, a % b); } int minArraySum(vector<int>& nums) { int g = nums[0]; for (int i = 1; i < nums.size(); i++) { g = gcd(g, nums[i]); } return g * nums.size(); } ``` 6. **给一个数字,求最多可以由几个质数和组成** - 考点:数论(哥德巴赫猜想:任意大于2的偶数可以表示为两个质数之和,任意大于5的奇数可以表示为三个质数之和) - 思路:根据哥德巴赫猜想,分类讨论: - 如果n是质数,则1个质数即可(即本身)。 - 如果n是偶数(且大于2),则最多两个质数(根据哥德巴赫猜想),但这里要求最多由几个质数和组成,实际上可以拆成多个质数之和(比如3+3=6,但也可以2+2+2=6,所以最多可以拆成n/2个2,即n/2个质数)。但题目要求最多,所以我们要尽可能拆成多个质数,那么就是尽可能拆成2和3(因为2和3是最小的质数,个数多)。 - 结论:当n>=2时,最大个数为:如果n是偶数,则最多为n/2(全部拆成2);如果n是奇数,则拆成一个3和剩下的偶数(即(n-3)/2 + 1 = (n-1)/2)。 - 但是注意:1不是质数,所以当n=1时,无法表示,返回0;n=2或3时,只能拆成1个(本身)。 - 然而,哥德巴赫猜想还有更深的结论:任意大于2的整数都可以表示为不超过三个质数的和(但这里要求最多个数,所以我们要尽可能多)。实际上,我们可以用2和3来拆,那么: 偶数:n = 2+2+... -> 个数为n/2 奇数:n = 3 + 2+2+... -> 个数为1 + (n-3)/2 = (n-1)/2 - 但是,这个结论并不完全正确,因为有些数可能拆得更多?比如9:可以拆成3+3+3(3个),而按照上面的公式:(9-1)/2=4,但实际最多只能拆成3个。所以我们需要修正:实际上,除了2和3,其他质数都是奇数,所以为了个数多,我们尽量用2和3。因此,我们使用以下策略: n=2 -> 1(一个2) n=3 -> 1(一个3) n=4 -> 2(两个2) n>=5: 如果n是偶数,则全部用2,个数为n/2。 如果n是奇数,则用一个3,剩下的n-3(偶数)用2,个数为1+(n-3)/2 = (n-1)/2。 - 但是,这样算出来的个数并不总是最大的?比如7:用3+2+2(3个),而7本身是质数,但拆开可以3个,比1个多。所以我们的目标就是尽可能拆成多个质数(每个质数尽可能小),那么就是2和3。 - 因此,最大个数为:当n>=2时,最大个数为n/2(向下取整)?不对,因为3比2大,所以当n比较大的时候,用3代替两个2(即3+3=6,相当于两个3,而2+2+2=6是三个2,所以三个2比两个3个数多)。所以为了个数多,我们尽量用2。 - 因此,对于任意n>=2,我们可以全部用2,除了当n为奇数时,需要用一个3(因为奇数-3=偶数,然后全部用2)。这样,个数为:n为偶数时,n/2;n为奇数时,(n-3)/2+1 = (n-1)/2。 - 但是,有一个例外:当n=1时,0个。 - 因此,代码为: if n==1: 0 else if n==2: 1 else if n==3: 1 else: if n%2==0: n/2 else: (n-1)/2 [注意:这里(n-1)/2是整数除法,因为n是奇数,所以n-1是偶数] - 但是,这样计算9:9是奇数,则(9-1)/2=4,但是9=2+2+2+3(4个)?不对,因为9=3+3+3(3个)比4个要少?不对,我们要求的是最多个数,所以我们要尽可能拆成多个质数,那么9=2+2+2+3(4个)是可行的(2+2+2+3=9),所以4个。而3+3+3只有3个。所以4>3,因此最多4个。 - 所以上述方法正确:因为奇数n,我们可以拆成1个3和(n-3)个2,而(n-3)是偶数,所以个数为1+(n-3)/2 = (n-1)/2(整数除法,因为n是奇数,n-1是偶数,所以整除)。 - 验证:n=9 -> (9-1)/2=4,正确。 - 但是,有没有可能不用3而用更大的质数?那样个数会变少。所以我们的策略是最优的。 - 代码示例: ```cpp #include <iostream> using namespace std; int main() { int n; cin >> n; if (n < 2) { cout << 0 << endl; } else if (n == 2 || n == 3) { cout << 1 << endl; } else { if (n % 2 == 0) { cout << n / 2 << endl; } else { cout << (n - 1) / 2 << endl; } } return 0; } ``` 7. **给定一个数组,求最长连续子序列,要求子序列的乘积不为0** - 考点:数组遍历、连续子序列、条件判断 - 思路:注意乘积不为0,即子序列中不能包含0。因此,问题转化为:求数组中最长的连续非零子序列的长度。实际上,就是遇到0就重置当前长度,否则当前长度+1,并更新最大值。 - 代码示例: ```cpp #include <vector> #include <algorithm> using namespace std; int longestNonZeroProductSubarray(vector<int>& nums) { int maxLen = 0, curLen = 0; for (int i = 0; i < nums.size(); i++) { if (nums[i] != 0) { curLen++; } else { maxLen = max(maxLen, curLen); curLen = 0; } } maxLen = max(maxLen, curLen); // 处理最后一段 return maxLen; } ``` 8. **球面几何,给出位置和速度,以及半径,求脱离球面的时间** - 考点:物理运动学、向量运算、解方程 - 思路:假设球心在原点,初始位置为P0(向量),速度为V(向量),球半径为R。脱离球面的条件是:位置向量的模大于R。即 |P0 + t*V| > R (t为时间)。 - 解不等式:|P0 + tV|^2 > R^2 - 展开: (P0·P0) + 2t(P0·V) + t^2(V·V) > R^2 - 整理: t^2(V·V) + 2t(P0·V) + (P0·P0 - R^2) > 0 - 这是一个关于t的一元二次不等式。注意,如果V·V=0(即速度为0),则不会移动,需要判断初始位置是否在球内。 - 一般情况下,二次函数开口向上(V·V>0),所以解为两根之外。我们需要找到最小的正实数t使得不等式成立(即脱离球面的时间)。 - 步骤: 1. 计算点乘:d0 = P0·P0, d1 = P0·V, d2 = V·V 2. 计算判别式:delta = 4*d1*d1 - 4*d2*(d0-R*R) 3. 如果d2==0:则速度为0,如果d0>R*R,则已经脱离(t=0);否则不会脱离(返回-1) 4. 如果判别式<0,则整个函数恒正(即始终在球外?)但初始位置在球内(d0<=R*R)则不会脱离?注意题目可能假设初始在球内。 5. 实际上,题目可能假设初始在球面上(d0=R*R)?题目没有明确,但通常脱离问题初始在球面上。 6. 因此,我们假设初始位置满足d0=R*R(在球面上),然后求t>0使得不等式成立。 7. 方程变为:t^2*d2 + 2t*d1 + (R^2-R^2)=0 -> t*(d2*t+2*d1)=0 - 一个根t=0(初始时刻),另一个根t=-2*d1/d2 - 当d1>=0时,速度方向与位置向量方向夹角小于90度(即向外运动),则t>0时,当t>-2*d1/d2(如果d2>0)时,不等式成立。由于t>0,所以当d1>0时,另一个根为负,所以对于所有t>0都满足(即立即脱离?但初始在球面上,所以t=0时在球面上,t>0就脱离?所以最小时间就是0+?但题目要求脱离,所以严格大于,所以最小时间应该是大于0的任意时间?但题目要求时间,应该是0?) - 但注意:题目要求脱离球面,即第一次大于R的时刻。由于初始在球面上,所以只要移动一点(速度不为0)就会脱离。但若速度方向是切向,则可能不会脱离(即d1=0)。所以需要分类: - 若d1>0:速度方向有向外的分量,则t>0后立即脱离,所以最小时间为0(但严格来说,0时刻在球面上,脱离需要大于0的时间,但题目可能要求第一次大于R的时刻,我们可以取一个极限,即t趋近0+,但实际计算中,我们取0?) - 若d1<0:速度向里,则不会脱离(会先进入球内,然后可能再出来?但题目可能只考虑脱离,即从球内到球外,而初始在球面上,向里运动就进入球内,所以不会脱离?) - 因此,题目需要明确初始状态和速度方向。通常,脱离球面是指从球内到球外,而初始在球面上,所以: - 如果速度方向向外(即d1>0),则脱离时间为0(但严格来说,脱离是大于0的时刻,但题目可能要求0,因为初始时刻已经算脱离?或者要求大于0的最小时间?但连续时间,最小时间不存在(任意小),所以通常题目会要求第一次达到球外的时间,即t=0+,但无法表示,所以可能题目设定为在球内,然后脱离球面(即穿过球面)的时间。 8. 重新考虑:题目没有明确初始位置,但一般脱离问题可能是从球内开始运动,穿过球面到球外。所以初始位置在球内(d0<R^2)。那么,我们要求t使得|P0+tV|>=R,最小t满足该等式(即第一次到达球面)?但题目要求脱离球面,应该是从球内到球外,所以穿过球面的时刻。而穿过球面可能有两次(进和出),我们要求第一次脱离球面(即离开球体)的时间,应该是第一次穿过球面的时间(即较小的正根)?但离开球体应该是第二次穿过(从球内到球外)?所以需要明确。 9. 题目要求“脱离球面”,通常理解为从球内运动到球外,所以脱离时刻是位置向量的模第一次大于R的时刻。但这样需要解不等式,且是连续时间,所以通常我们解方程|P0+tV|=R,得到两个根t1和t2(t1<t2),则当t>t2时,位置在球外。所以脱离球面的最小时间应该是t2?但题目可能要求从球内出发,第一次脱离(即t2)?但初始位置可能在球内也可能在球外?题目没有明确。 10. 鉴于题目描述不清晰,我们假设初始位置在球内(即d0<R^2),且速度方向向外(即d1>0),那么方程|P0+tV|=R有两个根,取最小的正根t1(第一次穿过球面,即离开球内到球外),则t1即为脱离球面时间。 11. 因此,算法: - 计算d0 = P0·P0, d1 = P0·V, d2 = V·V - 解方程:d2*t^2 + 2*d1*t + d0 - R^2 = 0 - 判别式:delta = 4*d1^2 - 4*d2*(d0-R^2) - 如果delta<0,则无实根,说明不会穿过球面(始终在球内运动),返回-1(无法脱离) - 否则,计算两个根:t1 = [-2*d1 - sqrt(delta)] / (2*d2) = [-d1 - sqrt(delta/4)] / d2 t2 = [-d1 + sqrt(delta/4)] / d2 - 由于d0<R^2,所以常数项为负,所以两个根一正一负?或者两个正根?需要根据d1判断。 - 实际上,因为d0<R^2,所以方程有两个根,且由韦达定理,t1*t2=(d0-R^2)/d2<0,所以两根异号。因此,我们取正根:t = t2 = [-d1 + sqrt(delta/4)] / d2 (因为d1可能是负的,但这里d1>0时,t2为正;d1<0时,t2也为正?因为-d1>0,而sqrt为正) - 但注意,t1和t2中,t1是负根,t2是正根。所以脱离时间就是t2。 - 但是,如果d1<0(速度方向向内),则t2可能为正,但此时初始在球内,速度向内,会先到达球面的另一侧(即穿过球心方向)?但题目要求脱离球面(即从球内到球外),那么当速度方向向内时,不会脱离球面(会先到达球心然后从另一侧脱离?)所以需要判断速度方向。 - 实际上,如果速度方向向内,且初始在球内,那么它不会脱离球面(除非速度足够大穿过整个球体,从另一侧脱离)。所以脱离球面的时间应该是第二次穿过球面的时间(即t2,因为t1是负,t2是正,且t2是第二次穿过,从球内到球外)。但注意,运动轨迹:从球内开始,先到达球面(第一次穿过,进入球内?不对,初始在球内,然后向内运动,会先到达另一侧的球面(即第一次穿过球面,此时是离开球体?)所以第一次穿过球面就是脱离?所以应该是t1?但t1是负?不对,所以需要重新考虑。 - 重新考虑:设初始位置向量P0,|P0|<R。速度V。方程:|P0+tV|^2 = R^2 - 展开:t^2*|V|^2 + 2t(P0·V) + |P0|^2 - R^2 = 0 - 判别式:D = 4(P0·V)^2 - 4|V|^2(|P0|^2-R^2) >0 (因为|P0|^2-R^2<0) - 所以有两个实根。设t1<t2,则t1=[-2(P0·V)-sqrt(D)]/(2|V|^2),t2=[-2(P0·V)+sqrt(D)]/(2|V|^2) - 由于常数项为负,所以两根异号,即t1<0,t2>0。 - 物理意义:t2表示从初始位置沿速度方向运动,穿过球面到达球外(因为t2>0,且t2时刻位置在球面上,之后将大于R)。所以脱离球面的最小时间就是t2(因为t2是第一次到达球面(从球内到球外)的时刻?但注意,在t2时刻,刚好在球面上,而题目要求脱离球面(即大于R),所以严格来说,脱离球面的时间应该大于t2,但题目可能要求到达球面的时间(即脱离球面的临界点)。 - 因此,我们取t2作为脱离时间。 - 但是,如果速度方向是向外的(P0·V>0),那么t2就是脱离时间;如果速度方向是向内的(P0·V<0),那么t2也是正的,但此时物体先向球心运动,然后穿过球心,在t2时刻到达另一侧的球面(脱离球体)。所以两种情况,脱离时间都是t2。 12. 所以,无论速度方向如何,只要初始在球内,则脱离时间为t2(正根)。 13. 特殊情况:如果初始在球外(|P0|>R),则题目可能不适用。但题目没有说明,我们假设初始在球内。 - 代码示例(假设初始在球内): ```cpp #include <iostream> #include <cmath> using namespace std; struct Vec3 { double x, y, z; double dot(const Vec3& other) const { return x*other.x + y*other.y + z*other.z; } double normSq() const { return x*x + y*y + z*z; } }; int main() { Vec3 P0, V; double R; cin >> P0.x >> P0.y >> P0.z; cin >> V.x >> V.y >> V.z; cin >> R; double d0 = P0.normSq(); double d1 = P0.dot(V); double d2 = V.normSq(); // 判别式 double delta = 4*d1*d1 - 4*d2*(d0 - R*R); if (delta < 0) { cout << -1 << endl; // 无解,不会脱离 } else { // 计算两个根 double t1 = (-2*d1 - sqrt(delta)) / (2*d2); double t2 = (-2*d1 + sqrt(delta)) / (2*d2); // 取正根(因为t1<t2,且t1为负,t2为正) if (t2 < 0) { cout << -1 << endl; // 两个根都是负的,不可能(因为d0<R^2,所以常数项负,所以两根异号) } else { cout << t2 << endl; // 脱离时间 } } return 0; } ``` 9. **已知十进制数字,可以在任意位置切割或者不切割,并且切割之后,数字求和,求和的种类数目** - 考点:动态规划、字符串处理 - 思路:将数字字符串切割成若干段,求所有切割方案得到的数字之和的种类数。注意,不同切割方案可能得到相同的和。 - 例如:"12":可以切割成"1"和"2"(和为3),也可以不切割("12",和为12),所以有两种不同的和。 - 问题转化为:求所有切割方案下,得到的数字之和的不同值有多少个。 - 动态规划:设dp[i]表示前i个字符切割后能得到的和的集合(集合去重)。 - 状态转移:对于第i个字符(0-indexed),枚举最后一段的起始位置j(j从0到i),则最后一段为s[j:i](注意字符串子串),然后该段对应的数字为num = stoi(s.substr(j, i-j+1)),然后dp[i] = { x+num | x属于dp[j-1] } (注意j=0时,前面没有字符,则dp[-1]视为{0})。 - 初始:dp[-1] = {0}(用-1表示空,实际实现时,我们可以用dp[0]表示前0个字符,和为0;然后从i=0开始遍历到n) - 最终:dp[n-1]的集合大小(即前n个字符切割后得到的所有和)即为答案。 - 但是,字符串长度可能很大(比如100位),那么切割方案有2^(n-1)种,和的最大值可能非常大(比如n位数,最大和是n*9,但切割后每段数字最大可能是10^100,所以无法直接求和)。 - 因此,需要优化:我们只关心不同和的值,但和的值域太大,无法用数组存。但是,题目要求的是种类数,我们可以用集合,但状态数可能达到2^(n-1)种,n最大100,则2^99太大,无法接受。 - 换一种思路:用DFS+记忆化搜索,但同样面临状态爆炸。 - 另一种思路:用滚动数组,用哈希表存储当前状态的所有和,然后递推。 - 设dp[i]为前i个字符切割后所有可能的和的集合。 - 初始化:dp[0] = {0}(还没有切割任何数字) - 然后,对于i从1到n(n为字符串长度),dp[i] = 所有j(0<=j<i)的:dp[j]中的每个数 + s[j:i](子串)对应的数字 - 最后,dp[n]的大小就是答案。 - 但是,最坏情况下,集合大小是指数级的,而n最大100,所以2^99太大。 - 因此,需要优化:观察问题,我们发现,切割后每段数字都是原字符串的一个连续子串,而和就是这些子串对应的数字之和。所以,问题转化为:求原字符串的所有连续子串划分(partition)对应的数字之和的种类数。 - 有没有可能用数学方法?或者用位运算优化?或者用map优化空间?但状态数依然是指数级。 - 另一种思路:用区间DP,但同样要记录和,和的值域太大。 - 题目可能只关心种类数,而和的值虽然大,但不同的和的数量可能不会太多?最坏情况下,不同的和的数量也是指数级(2^(n-1)),所以当n=100时,2^99约为6e29,无法计算。 - 因此,我们需要更高效的算法。注意:题目要求的是“不同和”的个数,而和的值很大,但我们可以用大整数?但计算量太大。 - 或者,我们可以用深度优先搜索配合哈希表去重,但n=100时,切割点有99个,每个切割点可以选择切或不切,共2^99种,无法枚举。 - 所以,本题可能n不大(比如n<=20),或者有更优的解法。 - 考虑n<=20的情况:我们可以用状态压缩DP?状态2^(n-1)种,n=20时,2^19=50万,可以接受。但n=100不行。 - 题目没有说明n的范围,但输入是十进制数字,所以字符串长度可能很大。因此,我们需要高效算法。 - 有一个性质:不同的划分方案可能得到相同的和。例如:"111":可以切为"1"、"1"、"1"(和为3),也可以切为"11"和"1"(和为12+1=13? 不对,11+1=12)-> 不对,所以没有重复。但比如"1001":切为"100"和"1"(100+1=101),切为"10"和"01"(10+1=11)-> 不同。所以重复情况较少,但最坏情况下,不同和的数量可能达到指数级。 - 因此,本题可能只适用于小规模输入(n<=30),或者用数学方法。 - 但是,题目没有给出n的范围,所以这里我们给出一个适用于小规模(n<=20)的DP解法。 - 代码示例(小规模): ```cpp #include <iostream> #include <vector> #include <unordered_set> #include <string> #include <cctype> using namespace std; int main() { string s; cin >> s; int n = s.size(); // dp[i] 表示前i个字符切割后所有可能的和的集合 vector<unordered_set<long long>> dp(n+1); dp[0].insert(0); for (int i = 1; i <= n; i++) { unordered_set<long long> newSet; // 枚举最后一段的起始位置j(最后一段为[j, i-1]) for (int j = 0; j < i; j++) { // 提取子串s[j:i] string sub = s.substr(j, i-j); // 转换为数字(注意前导零,但题目是十进制数字,所以前导零不影响数值?但如"00"是0,但题目输入是数字,不会有前导零?但切割后可能有,比如"01"是1,但转换会去掉?) // 注意:如果子串以'0'开头且长度大于1,则这个数字是无效的(因为不能有前导零)?但题目没有说,我们假设切割后得到的数字就是按字符串转整数,即允许前导零(但整数前导零自动忽略,所以"01"就是1)。 // 因此,如果子串以'0'开头且长度大于1,我们仍然转换,stoll会去掉前导零(但这样会丢失信息,比如"00"和"0"都是0,但实际不同?但和相同,所以不影响) // 但注意:如果子串很长(超过long long范围),则无法转换。所以n不能太大(比如n<=20,则最大19位数,在long long范围内) long long num = stoll(sub); // 遍历dp[j]中的每个值,加上num for (auto x : dp[j]) { newSet.insert(x + num); } } dp[i] = newSet; } cout << dp[n].size() << endl; return 0; } ``` ### 总结 以上对每个题目进行了分析,并给出了相应的代码示例。注意,部分题目(如第8题和第9题)在实现时需要注意边界条件和特殊情况的处理。 --- §§相关问题§§: 1. 在第5题中,如何证明数组操作的最小和就是n倍的数组元素的最大公约数? 2. 第6题中,如果要求使用尽量少的质数(而不是最多的质数)来组成给定数字,如何求解? 3. 第8题中,如果初始位置在球外,且速度方向指向球内,求进入球面的时间,如何修改算法? 4. 第9题中,如果数字字符串很长(例如长度100),如何高效地求解不同和的数目(避免指数级复杂度)?</think>### 代码原理分析 这段代码实现了将数字字符串转换为**保留两位有效数字的科学计数法**,并进行**四舍五入处理**。核心功能是将任意长度的数字字符串(如"1234")转换为科学计数法形式(如"1.2*10^3"),其中第二位数字根据第三位数字进行四舍五入。 ### 代码语法解析 ```cpp #include <iostream> #include <string> using namespace std; int main() { string s; cin >> s; // 读取输入的数字字符串 // 提取前三位数字 int a = s[0] - '0'; // 第一位有效数字(必存在) int b = (s.size() > 1) ? (s[1] - '0') : 0; // 第二位有效数字(不存在则为0) int c = (s.size() > 2) ? (s[2] - '0') : 0; // 第三位数字(用于四舍五入) int exp = (int)s.size() - 1; // 计算指数(10的幂次) // 四舍五入处理 if (c >= 5) { // 第三位≥5时进位 b++; // 第二位数字+1 if (b == 10) { // 处理进位到第一位 b = 0; a++; if (a == 10) { // 处理首位进位(如999→1000) a = 1; b = 0; exp++; // 指数增加(科学计数法规格化) } } } // 输出结果(格式:a.b*10^exp) cout << a << "." << b << "*10^" << exp << "\n"; return 0; } ``` ### 核心算法详解 1. **数字提取与初始化**: - `a` = 第一位数字(强制存在) - `b` = 第二位数字(不存在则补0) - `c` = 第三位数字(用于四舍五入判断) - `exp` = 指数 = 字符串长度 - 1(科学计数法的10的幂次) 2. **四舍五入规则**: ```plaintext if c ≥ 5: b = b + 1 if b == 10: b = 0 a = a + 1 if a == 10: a = 1 b = 0 exp = exp + 1 ``` 3. **进位场景分析**: - **无进位**:`c < 5` → 直接输出`a.b` - **简单进位**:`c ≥ 5` 且 `b < 9` → `b+1` - **跨位进位**:`b=9`进位 → `b=0, a+1` - **首位进位**:`a=9`进位 → `a=1, b=0, exp+1`(如999→1000) ### 科学计数法原理 科学计数法标准形式: **N = a.b × 10<sup>exp</sup>** 其中: - `a` ∈ [1,9](首位非零数字) - `b` 是第二位有效数字 - `exp` = 数字总位数 - 1 ### 示例测试 | 输入 | 处理过程 | 输出 | 解释 | |----------|------------------------------|-----------------|-------------------------------| | "5" | a=5, b=0, c=0 → 不进位 | 5.0*10^0 | 5 = 5.0×10⁰ | | "123" | a=1,b=2,c=3<5 → 不进位 | 1.2*10^2 | 123 ≈ 120 = 1.2×10² | | "128" | a=1,b=2,c=8≥5 → b进位 | 1.3*10^2 | 128 ≈ 130 = 1.3×10² | | "999" | c=9≥5 → b进位→a进位→指数进位 | 1.0*10^3 | 999 ≈ 1000 = 1.0×10³ | | "100" | a=1,b=0,c=0<5 → 不进位 | 1.0*10^2 | 100 = 1.0×10² | | "1999" | a=1,b=9,c=9≥5 → 进位至指数 | 2.0*10^3 | 1999 ≈ 2000 = 2.0×10³ | | "0" | a=0,b=0,c=0 → 不进位 | 0.0*10^0 | 0 = 0.0×10⁰ | ### 算法特点 1. **时间复杂度**:O(1) 仅处理前3个字符,与输入长度无关 2. **空间复杂度**:O(1) 仅使用固定数量的变量 3. **边界处理**: - 单字符输入自动补零("5"→5.0) - 首位进位自动调整指数(999→1.0×10³) - 空输入会导致崩溃(未处理异常) ### 局限性 1. **负数处理**:不支持负号识别 2. **前导零**:未处理如"0012"的非法格式 3. **小数支持**:仅支持整数格式输入 4. **超大数**:第三位之后的数字被忽略 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值