【丢失的数字】遇到__i64和long long的坑

本文探讨了一道编程竞赛题目,使用 C++ 和 map 数据结构来找出指定范围内缺失的数字个数。通过对比两种代码实现,解释了使用 __int64 类型而非 longlong 的必要性,并成功通过评测。


给出m个数字,请你找出1到n中没有出现在这m个数字中的数字个数。Input多组输入,每组输入第一行n和m,接下来一行m个数字a[i]。 
数据范围:1<=n,m<=100000,-1e9<=a[i]<=1e9。Output每组输出一行,表示丢失的数字的个数。Sample Input
5 3
1 3 4
4 6
1 3 8 3 11 12
Sample Output
2
2



这里其实是可以不用long long 长整型的,但是我为了保险,还是在代码里面加了长整型,但是这里却卡在了g++的编译规则上面,下面看两个代码,第一个是WA的,第二个是AC的,注意比较两个代码的不同:



#include<cstdio>
#include<map>
using namespace std;

int main(void)
{
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF){
		
		map<long long,int> mp;
		long long tmp;
		
		for(int i = 0 ; i < m ; ++i){
			
			scanf("%lld",&tmp);
			if(tmp>n||tmp<1) continue;
			if(mp.find(tmp)==mp.end()) mp[tmp]=i;
		}
		printf("%d\n",n-mp.size());
	}
	
	
	return 0;
 } 



#include<cstdio>
#include<map>
using namespace std;

int main(void)
{
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF){
		
		map<__int64,int> mp;
		__int64 tmp;
		
		for(int i = 0 ; i < m ; ++i){
			
			scanf("%I64d",&tmp);
			if(tmp>n||tmp<1) continue;
			if(mp.find(tmp)==mp.end()) mp[tmp]=i;
		}
		printf("%d\n",n-mp.size());
	}
	
	
	return 0;
 } 


第二个代码我只是将map中和tmp的long long 改成了__int64,输入lld改为 i64d,然后就AC了,至于原因,这个博客已经写的很详细了有兴趣的可以去看看  点击打开链接






这是之前的思考过程,请继续补全: 题目要求:给定一个数字字符串(可能很长,所以用字符串处理),我们可以在任意两个字符之间插入加号或者不插入,形成表达式。但要求每个分割出的数字不能有前导零(除非本身就是0,但题目要求加数大于0,所以不可能有0,因此实际上每个数字不能以'0'开头,除非是一位数的0,但题目要求加数大于0,所以每个数字至少为1,因此不能出现以0开头的多位数)。 需要计算: 1. 方案数(即合法的插入加号的方式) 2. 所有方案中,表达式的最大值、最小值,以及所有方案表达式的总。 注意:表达式可以是整个数字(不加任何加号)。 例如:"1023"有4种方案:1023, 10+23, 102+3, 10+2+3。 但是注意:10+23中的"023"是不合法的,因为"023"有前导零。然而在样例1中,10+23是合法的,因为23没有前导零。而01这种是不合法的。 因此,在分割时,如果某一段的第一个字符是'0'且这一段长度大于1,那么就是非法的。 思路: 我们可以使用动态规划或者深度优先搜索(DFS)来枚举所有可能的分割位置。由于数字长度最大可能达到18(从数据范围看,测试点9~10是10^18,所以字符串长度最大18),因此我们可以用状态压缩或者直接DFS+记忆化,或者动态规划。 由于长度最多18,我们可以用O(2^(n-1))的枚举,即枚举每个间隔(共n-1个间隔)是否插入加号,然后检查每个分割出来的数字是否合法(无前导零,除非单个0,但题目要求加数大于0,所以单个0也不允许?注意题目要求:数字不能包含前导0,并且加数大于0。所以如果遇到0,那么只能单独作为一个数?但是题目要求加数大于0,所以0是不允许的。因此,如果字符串中有0,那么它只能出现在一个非零数字后面吗?不对,实际上题目要求每个加数都是正整数,所以不能有0(包括单个0也不允许)。因此,在分割时,一旦某一段第一个字符是'0',那么这一段就不合法(因为长度大于1,或者长度为1但是0也是不允许的)。 所以,在枚举分割时,需要跳过那些以0开头的多位数(即长度>1且第一个字符为'0')的段,以及单独一个0也是不允许的(因为题目要求加数大于0)。 因此,我们需要在枚举时检查每一段是否合法:如果这一段的第一个字符是'0'且长度大于1,则非法;否则(即第一个字符不是'0',或者长度等于1且是0?但0不允许,所以实际上我们要求每一段都不能以0开头(除非是0本身,但是0又不允许,所以干脆不允许任何一段包含0?不对,比如10,如果我们分割成10,那么0是不允许的。所以实际上,每一段必须满足:要么是一个大于0的数且没有前导零(即如果长度大于1,则第一个字符不能是'0';如果长度等于1,可以是0吗?不行,因为题目要求加数大于0,所以任何一段都不能是0(包括单个0))。 因此,在分割时,每一段必须满足:第一个字符不是'0'(那么长度大于1时没有前导零)且整个段代表的数字大于0(自然满足,因为第一个字符不是0,且至少是一位数)。注意:如果整个字符串就是"0"呢?但题目要求加数大于0,所以输入不会是0?但是题目没有明确说明,但是根据样例,输入都是正整数,所以我们可以认为输入字符串第一个字符不是0(除非长度为1的0)。但是题目数据范围n≤10^18,所以最小为1,所以不会出现0。 所以我们只需要检查每一段:如果该段长度大于1且第一个字符是'0',则跳过;或者该段为"0"(单独一个0)则跳过(因为0不允许)。但实际上,因为输入字符串可能包含0吗?比如"1023",其中就包含0,但是分割时不能把0单独作为一段(因为0不允许),所以分割时遇到0开头的段且长度大于1,我们直接跳过。但是注意:如果一段是"0",即单独一个0,那么也是不允许的,所以也要跳过。 然而,整个表达式就是一个数字,这个数字可以是0吗?题目要求加数大于0,所以整个表达式如果是0,那么也是不允许的?但是题目说“不加入加号也可以”,所以整个表达式就是一个数字,这个数字必须大于0(因为题目要求加数大于0,所以这个数字也必须大于0)。因此,如果整个字符串是"0",那么没有合法方案。但是题目数据范围是正整数,所以不考虑0。 所以,我们枚举所有分割方案(2^(n-1)种),然后检查每一段是否合法(每一段第一个字符不能是'0'(这样保证了没有前导零,且长度大于1时不会出现0开头,同时也保证了不会出现单独0的情况,因为单独0的第一个字符就是0,所以会被排除))。 但是,注意:一段如果是"0"(单独一个0)是不允许的,而如果一段是"01"(有前导零)也是不允许的。而我们的检查:如果第一个字符是'0',那么不管后面是什么,只要长度大于1就不允许;而如果长度等于1且是0,那么也是第一个字符为0,所以也会被检查到。因此,我们只需要判断:对于每一段,如果该段长度大于1且第一个字符是'0',或者该段长度等于1且该段为"0",那么就不合法?实际上,因为题目要求加数大于0,所以不允许0出现,因此只要一段的第一个字符是'0',那么这段就不合法(因为长度大于1时,有前导零;长度等于1时,该数为0,也不合法)。所以我们可以统一为:只要该段第一个字符是'0',就不合法。 但是,有一种情况:一段是"0x"(x是任意数字),那么第一个字符是0,所以非法。而一段是"00",也是非法。一段是"0"(长度1),也是非法。 因此,我们只需要在枚举分割方案时,对每一段检查:如果该段的第一个字符是'0',那么跳过该方案。 具体步骤: 1. 方案数:用位运算枚举所有间隔的选择情况(0表示不插入,1表示插入)。总共有2^(n-1)种情况。 2. 对于每一种枚举,我们将字符串按照1的位置进行分割,然后检查每一段是否合法(即每段的第一个字符不能是'0')。 3. 如果合法,则计算该表达式的(将每一段转换成数字相加)。 4. 记录方案数,并更新最大值、最小值,以及累加总。 但是,注意:n最大为18,那么间隔有17个,2^17=131072,这是可以接受的。 但是,计算总的时候,如果每次都将字符串转换成数字,转换操作的时间复杂度为O(n),那么总时间复杂度为O(2^(n-1)*n),最大为131072*18≈2.36e6,可以接受。 但是,我们也可以预处理区间数字值,避免重复转换。我们可以用dp[i][j]表示从i到j(包括ij)形成的数字。但是这里n最大18,预处理也可以,但枚举分割时还是要一段段取,不如直接转换。 不过,为了避免重复转换,我们可以用记忆化:在枚举分割时,我们按分割点取子串,然后转换成数字。由于子串很多重复,但总子串数量是O(n^2)的,所以我们可以提前预处理所有子串对应的数字。但是n只有18,子串数量大约18*18/2=162,所以预处理也可以。 但是,为了简单,我们可以直接转换,因为2^17*18的转换次数并不大。 另一种思路:动态规划 设dp[state]?但状态不好表示。我们要求的是总,最大值,最小值,方案数,所以我们可以用四个数组: dp_count[i]:表示前i个字符构成的合法方案数。 dp_max[i]:前i个字符构成的所有合法表达式的的最大值。 dp_min[i]:前i个字符构成的所有合法表达式的的最小值。 dp_sum[i]:前i个字符构成的所有合法表达式的总。 但是,注意:表达式是求,所以整个表达式是多个加数的。那么,我们考虑最后一个加数,它可以是[j...i](j从1到i)这一段。那么转移: 如果[j...i]这一段是合法的(即s[j]!='0',或者如果j==i且s[j]=='0'?但0不允许,所以要求s[j]!='0'),那么: dp_count[i] += dp_count[j-1] (注意j从1开始,那么j-1就是上一段结束的位置,注意字符串下标从0开始) 但是,我们这样定义:字符串下标0..i-1(共i个字符),那么我们可以定义dp[0]=1(空串有一种方案,但注意空串后面没有数字,所以实际上我们dp[i]表示前i个字符的合法方案数,那么dp[0]=1作为边界,然后从1开始到n)。 转移: for j in range(0, i): # j是当前段的起始位置?或者结束位置?我们考虑当前段是[k, i-1] 令k从0到i-1,如果从k到i-1这一段是合法的,那么: dp_count[i] += dp_count[k] (但是注意,这里k表示前k个字符已经处理完了,那么当前段就是k到i-1,所以要求k<=i-1) 但是,这样定义的话,dp_count[k]表示前k个字符的合法方案数,然后当前段是[k, i-1](长度为i-k),那么总方案数就是dp_count[k] * 1(因为当前段只有一种情况,但注意,当前段本身是确定的,所以不需要乘方案数,只是将状态从k转移到i)。 同时,对于表达式的总,我们有: 设num = stoll(substr(k, i-k)) // 从k到i-1的子串对应的数字 那么,dp_sum[i] += dp_sum[k] + dp_count[k] * num 解释:对于前k个字符的每一种方案,整个表达式的都需要加上num(因为当前段作为一个加数)。所以,总 = 所有k的情况下(dp_sum[k] + dp_count[k]*num)之。 但是,注意:如果k=0(即当前段是整个字符串的第一段),那么dp_sum[0]是0,dp_count[0]是1(空串),那么表达式=0+1*num=num,正确。 对于最大值最小值: dp_max[i] = max{ dp_max[k] + num } ? 不对,因为dp_max[k]表示前k个字符的最大,然后加上当前段的数字num,所以是dp_max[k]+num。但是,这里要求k是从0到i-1且当前段合法。 同理,dp_min[i] = min{ dp_min[k] + num } 但是,注意:当k=0时,dp_max[0]应该是多少?因为前面没有数字,所以dp_max[0]应该是0,然后当前段是num,所以dp_max[i]至少是num。但是,如果整个字符串只有一段,那么表达式就是num。 因此,我们定义: dp_count[0] = 1 dp_sum[0] = 0 dp_max[0] = 0 // 注意,这里dp_max[0]不能设为负无穷,因为后面要加num,所以设为0。但是当i=0时,表达式应该是0?但实际表达式至少有一个数,所以当i>0时,我们不会使用dp_max[0]来更新最大值(因为后面有num,而num>0)?不对,当整个字符串作为一段时,k=0,那么dp_max[0]+num=0+num=num,这是正确的。 但是,我们要求表达式最小值,如果dp_min[0]设为0,那么dp_min[i]=min{ dp_min[k]+num },当k=0时,就是0+num=num。但是,如果整个字符串有多段,那么dp_min[k]可能更小?不对,因为dp_min[k]表示前k个字符的最小,然后加上当前段的num。 所以,动态规划的状态: 从0到n(n为字符串长度),dp_count[0]=1, dp_sum[0]=0, dp_max[0]=0, dp_min[0]=0? 但是注意,dp_min[0]如果设为0,那么当我们取第一段时,表达式就是num,这是正确的。但是,如果我们设dp_min[0]为0,那么当后面有多个段时,dp_min[k](k>0)可能比0大,那么取min时,0+num可能不是最小值。但是,实际上,dp_min[k](k>0)表示前k个字符的最小,这个至少为一个正数(因为每个加数>0),所以dp_min[k]至少大于0。因此,我们更新dp_min[i]时,考虑所有k,其中k=0时,dp_min[0]=0,所以0+num就是num,然后其他k(k>0)时,dp_min[k]>=某个正数,然后加上num,所以一定大于num?不对,因为dp_min[k]可能比num小吗?不一定,因为前面可能有多个加数,但是每个加数都是正数,所以dp_min[k]至少等于k个数字的最小,而num可能是一个很大的数(比如当前段很长)。所以,我们需要比较所有k对应的值。 但是,有一个问题:dp_min[0]为0,那么当我们取第一段时,表达式就是num(比如整个字符串作为一段,那么num就是整个数字,可能很大)。而如果我们不取整个字符串作为一段,而是分成多段,那么dp_min[k](k>0)可能更小。比如"12",分成1+2,那么dp_min[2] = min{ dp_min[0]+12, dp_min[1]+2 },其中dp_min[1]表示前1个字符的最小,即1(因为前1个字符只有一段:1),然后加上当前段2,得到3;而dp_min[0]+12=12。所以取min(12, 3)=3。正确。 因此,动态规划方程: 令s为输入字符串,下标0~n-1。 我们定义: dp_count[0] = 1 dp_sum[0] = 0 dp_max[0] = 0 // 注意,这里dp_max[0]不能是-INF,因为后面加正数,但考虑到最大值,如果设为0,那么dp_max[i]至少是num(当前段),这是正确的。 dp_min[0] = 0 // 这里dp_min[0]设为0,因为后面加正数,所以dp_min[i]至少为num(当前段),然后其他分割方式比较。 但是,dp_min[0]设为0,那么当i>0时,dp_min[i]会取到从0转移过来的值(即整个字符串作为一段)其他分割方式的最小值。正确。 但是,注意:dp_min[0]设为0,那么dp_min[0]表示空串的表达式为0?实际上,空串的表达式应该是0,但题目要求表达式至少有一个数,所以当我们加入第一段时,表达式就是num,所以0作为起点是合理的。 然后,对于i从1到n(dp数组的下标i表示前i个字符,即子串s[0:i]): dp_count[i] = 0 dp_max[i] = 一个很小的数(比如-1e18) dp_min[i] = 一个很大的数(比如1e18) dp_sum[i] = 0 然后,枚举最后一段的起点j(j从0到i-1),最后一段就是s[j:i](子串从j到i-1,长度为i-j)。注意:最后一段不能有前导零,所以如果s[j]=='0'并且i-j>1,则跳过(注意:如果最后一段长度为1且s[j]=='0',那么0也不允许,所以只要s[j]=='0',那么最后一段就不合法,跳过)。 如果合法,则计算num = stoll(s.substr(j, i-j)) // 注意:这里n最大18,所以不会溢出(long long可以表示18位数)。 然后: dp_count[i] += dp_count[j] dp_sum[i] += dp_sum[j] + dp_count[j] * num // 因为每种方案都要加上num,所以总共加 dp_count[j] * num dp_max[i] = max(dp_max[i], dp_max[j] + num) dp_min[i] = min(dp_min[i], dp_min[j] + num) 注意:这里j从0到i-1,当j=0时,最后一段就是整个字符串(前i个字符),此时dp_count[0]=1,dp_sum[0]=0,所以dp_count[i]加1,dp_sum[i]加0+1*num=num,dp_max[i]dp_min[i]分别更新为0+num=num。 但是,这样会漏掉什么吗?比如,我们枚举最后一段,那么前面的部分(0到j-1)已经是一个合法的表达式,然后加上最后一段。这确实覆盖了所有情况。 但是,注意:最后一段的起点j必须满足:前j个字符是已经处理过的,即dp_count[j]>0。如果dp_count[j]==0,说明前j个字符没有合法方案,那么也不能转移。 因此,我们要求dp_count[j]必须大于0。 但是,由于我们枚举j从0开始,且dp_count[0]=1,所以j=0总是可以转移的(只要最后一段合法)。 那么,整个字符串"0"的情况:如果字符串是"0",那么i=1,j=0,最后一段s[0:1]="0",此时因为s[0]=='0'且长度1,但题目不允许0,所以跳过。那么dp_count[1]就为0,没有合法方案。正确。 另外,如果字符串开头是0,比如"0123",那么当j=0时,最后一段为"0"(不合法),跳过;当j=1时,最后一段为"1"(合法),但前1个字符(即s[0:1]="0")的dp_count[1]是多少?因为前1个字符就是"0",所以dp_count[1]应该是0(因为"0"不合法),所以j=1时,dp_count[1]为0,不能转移。所以整个字符串"0123"没有合法方案?但是题目要求加数大于0,所以整个字符串"0123"作为表达式,实际上是0123,但整数123?不对,题目要求数字不能有前导零,所以0123是不合法的(因为0开头且长度大于1)。所以整个字符串不能作为合法表达式。所以确实没有合法方案。 因此,动态规划可以解决。 但是,注意:枚举最后一段时,j的范围是0到i-1,但注意:最后一段的起点j必须使得前j个字符是合法的(即dp_count[j]>0),否则无法转移。因此,我们只需要枚举j,并且当dp_count[j]>0且最后一段合法时,才进行转移。 但是,我们也可以不检查dp_count[j]>0,因为如果dp_count[j]==0,那么加上去也没用(加0)。所以可以不检查,直接枚举。 但是,我们初始化dp_count[0]=1,其他dp_count[i]初始为0,然后从i=1开始更新。 最后,答案就是: 方案数:dp_count[n] 最大表达式:dp_max[n] 最小表达式:dp_min[n] 表达式总:dp_sum[n] 但是,注意:样例1:s="1023", n=4 我们需要计算dp[4]。 转移时,枚举最后一段的起点j:j=0,1,2,3 j=0: 最后一段为"1023",因为s[0]=='1',所以合法。num=1023。 dp_count[4] += dp_count[0] = 1 dp_sum[4] += dp_sum[0] + dp_count[0]*1023 = 0+1*1023=1023 dp_max[4] = max(初始值, dp_max[0]+1023) = 0+1023=1023 dp_min[4] = min(初始值, 0+1023)=1023 j=1: 最后一段为s[1:4]="023",但是s[1]=='0'且长度3>1,不合法,跳过。 j=2: 最后一段为s[2:4]="23",合法,num=23。然后需要dp_count[2](前2个字符的合法方案数)是多少? 前2个字符"10":有两种分割方式吗?不,在dp[2]时: 计算dp[2]:枚举最后一段的起点k:k=0,1 k=0: 最后一段"10":合法,num=10 -> dp_count[2] += dp_count[0]=1, dp_sum[2]=10, dp_max[2]=10, dp_min[2]=10 k=1: 最后一段"0":不合法(因为s[1]=='0'且长度1,所以不允许0),跳过。 所以dp[2]={1,10,10,10} 因此,j=2:最后一段"23",那么: dp_count[4] += dp_count[2] = 1 -> 变为2 dp_sum[4] += dp_sum[2] + dp_count[2]*23 = 10 + 1*23 = 33 dp_max[4] = max(1023, 10+23)=1023 dp_min[4] = min(1023, 10+23)=33 j=3: 最后一段为s[3:4]="3",合法,num=3。需要dp_count[3](前3个字符的合法方案数)是多少? 计算dp[3]:枚举最后一段起点k:k=0,1,2 k=0: 最后一段"102" -> 合法?因为s[0]!='0',所以合法。num=102 -> dp_count[3] += dp_count[0]=1, dp_sum[3]=102, dp_max[3]=102, dp_min[3]=102 k=1: 最后一段"02" -> 不合法(s[1]=='0'且长度2>1),跳过。 k=2: 最后一段"2" -> 合法,num=2 -> 需要dp_count[2](前2个字符的方案数)=1,所以: dp_count[3] += dp_count[2] = 1 -> 总共2 dp_sum[3] += dp_sum[2] + dp_count[2]*2 = 10 + 1*2 = 12 dp_max[3] = max(102, 10+2)=102 dp_min[3] = min(102, 12)=12 所以dp[3]={2, 102+12? 不对,dp_sum[3]应该是102+12=114?不对,我们分别计算: k=0: 方案1:整个"102" -> 表达式=102 k=2: 方案2:前两个字符分成"10"(一段),然后加上"2" -> 表达式=10+2=12 dp_sum[3] = 102+12 = 114 dp_max[3]=102, dp_min[3]=12 然后j=3:最后一段"3": dp_count[4] += dp_count[3] = 2 -> 变为4 dp_sum[4] += dp_sum[3] + dp_count[3]*3 = 114 + 2*3 = 120 dp_max[4] = max(1023, 102+3, 12+3) -> 1023105,15比较,最大值还是1023 dp_min[4] = min(33, 102+3, 12+3)=min(33,105,15)=15 所以dp_sum[4]=1023+33+120=1176?不对,这里dp_sum[4]=1023(j=0) + 33(j=2) + 120(j=3)? 不对,我们每次j循环是累加: j=0: 1023 j=2: 33 -> 累计1023+33=1056 j=3: 120 -> 累计1056+120=1176 正确。 所以方案数=4,最大值=1023,最小值=15,总=1176。 但是,我们上面计算dp_sum[4]时,j=3的贡献是114+2*3=120,而dp_sum[3]是114(前3个字符的表达式的总),然后加上最后一段3,并且有两种方案(前3个字符有两种方案),所以每种方案都要加上3,所以是2*3,然后再加上前3个字符的表达式总114(因为每种方案都要加上3,所以相当于在原来的表达式上加上3乘以方案数)。 这个动态规划是正确的。 但是,注意:我们枚举最后一段的起点j时,最后一段是s[j:i](注意:i表示前i个字符,所以最后一个字符下标是i-1)。所以子串为s[j]到s[i-1]。 另外,我们使用long long,因为数字最大18位,所以不会溢出,但是总可能会很大?比如最多2^(n-1)种方案,n最大18,即2^17=131072种方案,每个表达式最大为10^18,那么总最大131072*10^18 ≈ 1.3e23,long long最大为9e18,所以会溢出。因此,我们需要使用更大的整数类型,比如unsigned long long(最大1e19)也不够,所以要用__int128(如果编译器支持)或者用高精度。但是题目数据范围:n最大18,但测试点9~10是10^18(这里的10^18是指数字的大小,但字符串长度最多18,所以分割方案最多2^17=131072种,表达式最大就是整个数字(10^18),那么总最大131072*10^18=1.3e23,这超过了long long(9e18)unsigned long long(1.8e19),所以我们需要用__int128(可以表示约3.4e38)或者用高精度。 但是,题目要求输出三个整数,但样例输出中,12345678的总是17577216(大约1e7),所以实际数据可能不会达到1e23。但是,我们看样例输入3:12345678,输出总17577216,这个值很小。为什么?因为分割后,每个加数都很小,所以总不会很大。但是,我们考虑最坏情况:整个数字非常大(比如10^18)且只有一种方案(整个字符串作为一段),那么总就是10^18。而方案数最多131072种,那么所有方案总最大就是131072 * 10^18 = 1.3e23,这个数超过了long long。 但是,题目数据范围中,测试点5~6是n≤100000,但是这里n是数字的位数?题目描述输入是一个整数n,但后面说“王老师拥有的数字”,所以输入是一个整数,可能很大,所以用字符串处理。题目数据范围表格中“n≤”指的是数字的位数?表格中测试点1:10,测试点2:100,测试点3~4:1000,测试点5~6:100000,测试点7~8:10,测试点9~10:10^18。这里表格中n≤100000,指的是数字的位数?100000位?但是2^(99999)是天文数字,枚举不可能。所以这里表格中的n应该是数字的值?不对,输入是一个数字,这个数字最大可以达到10^18(即18位),所以位数最多18位。 再看表格:测试点1:n≤10,这里n应该是数字的值(整数)?但是10是一位数,而测试点2:n≤100(两位数或三位数?100是三位数),测试点3~4:1000(四位数),测试点5~6:100000(六位数),测试点7~8:10(一位数或两位数?),测试点9~10:10^18(最多18位数)。 因此,实际上,数字的位数最多18位。所以我们的动态规划是O(n^2)的(因为i从1到18,j从0到i-1,总共18*18=324次循环),所以完全可以用O(n^2)的动态规划。 但是,为什么测试点5~6是100000?这里100000应该指的是数字的值不超过100000(即最多5位数),所以位数最多5位,那么动态规划只需要O(5^2)=25次循环。 所以,我们按照字符串长度来,长度最大18,所以O(n^2)的循环次数最多324次,完全可以。 但是,总可能会溢出long long吗?最大方案数:2^(len-1),len最大18,最大方案数2^17=131072。而每个表达式的最大为10^18,那么总最大131072 * 10^18 = 1.3e23,这个数超过了unsigned long long(1.8e19)long long(9e18)。所以我们需要用__int128来存储dp_sum。但是,题目输出要求是整数,而样例输出都是整数,且最小总15,最大1023,样例3总17577216,所以实际数据可能不会达到1e23。但是,我们为了保证正确性,还是用__int128。不过,注意:如果评测机不支持__int128,那么我们可以用高精度?但是题目数据范围中,总最大1.3e23,这个数有80位二进制,所以高精度比较麻烦。或者我们分析一下:最坏情况,数字是999...9(18个9),那么整个表达式就是10^18-1,方案数最多131072,那么总最大(10^18-1)*131072≈1.31e23,这个数可以用double近似表示?但题目要求精确整数。 但是,我们看样例3:12345678,方案数128,总17577216,这个数很小。为什么?因为分割后,每个加数都很小。实际上,最坏情况是每个分割点都插入加号,那么表达式=1+2+3+...+9(如果数字是123456789)?不对,数字是给定的,比如一个所有位都是1的18位数,那么总=方案数*(1+1+...+1)?不对,因为每个方案的不同。 但是,动态规划中,我们计算dp_sum[i]的公式:dp_sum[i] = sum_{j} (dp_sum[j] + dp_count[j]*num) 这个公式中,dp_count[j]dp_sum[j]在j递增时,会指数级增长(方案数是指数增长的),而num最大为10^18,所以总可能会很大。 因此,为了避免溢出,使用__int128。如果编译器不支持__int128,那么我们可以用long double来存?不行,因为要精确整数。或者用高精度。 但是,题目没有说明数据范围,但根据提示,数字位数最多18位,方案数最多131072,而表达式最大为整个数字(10^18),所以总最大131072*10^18=1.31e23,这个数可以用字符串表示,但输出时,题目要求输出整数,所以如果超过long long,我们需要写高精度输出。但是,题目样例输出都是普通整数,所以实际数据可能不会达到那么大。 我们再看样例输入2:1005,输出总1110。 1005有两种方案:10051+005(但005不合法,所以只有1005100+5?不对,样例输出2:2 1005 105 1110,所以两种方案:1005(=1005)100+5(=105),所以总=1005+105=1110。 所以,总就是所有方案的相加。 因此,我们使用__int128来存储dp_sum,dp_max,dp_min。但是dp_maxdp_min最大10^18,所以用long long也可以(因为10^18<2^60,而long long最大2^63-1,所以可以)。但是dp_sum可能很大,所以dp_sum用__int128。 但是,输出时,如果评测机不支持__int128,那么我们可以用字符串输出__int128?或者我们判断如果支持__int128就用,否则用高精度?这里我们假设评测机支持__int128(现在大多数OJ都支持)。 如果不支持,我们也可以用两个long long来模拟,或者用现成的高精度库,但题目要求只能用C++,而且代码不能有注释,所以我们可以自己写一个简单的__int128输出函数。 由于题目输出最后三个数,而dp_maxdp_min我们用long long存储(因为最大值最小值不会超过整个数字,而整个数字最大10^18,所以long long足够),dp_sum用__int128。 但是,动态规划中,dp_maxdp_min在转移时,需要加上num(long long),而dp_max[i]dp_min[i]我们定义为long long。 因此,我们定义: vector<long long> dp_count(n+1, 0); // 方案数,最多131072,long long可以 vector<__int128> dp_sum(n+1, 0); // 总,可能很大 vector<long long> dp_max(n+1, 0); // 最大值,最大10^18,所以用long long vector<long long> dp_min(n+1, 0); // 最小值 但是,dp_maxdp_min的初始值: dp_max[0]=0, dp_min[0]=0 dp_count[0]=1 dp_sum[0]=0 然后,对于i>=1,初始化dp_max[i]为一个很小的值,dp_min[i]为一个很大的值。但是,dp_max[i]dp_min[i]是long long,所以: dp_max[i] = LLONG_MIN; // 不行,因为转移时可能加上一个正数,然后比LLONG_MIN大,但是LLONG_MIN是一个很小的负数,如果我们没有转移成功,那么最后dp_count[i]可能为0,那么我们就不能输出这些值(题目要求输出三个整数,但如果没有合法方案,那么最大值最小值怎么定义?题目没说明,但样例至少有一组方案(整个字符串))。所以题目保证至少有一种方案(整个字符串,而且字符串是正整数,所以整个字符串是合法的,因为题目说输入是正整数)。所以我们可以不考虑无方案的情况。 但是,我们初始化dp_count[i]=0,如果最后dp_count[i]为0,那么我们就跳过。但是题目保证有方案(整个字符串),所以最后dp_count[n]>0。 因此,我们初始化: dp_max[0]=0, dp_min[0]=0 for i from 1 to n: dp_max[i] = LLONG_MIN; // 或者我们初始化为一个很小的数,但为了安全,初始化为一个不可能的值,然后如果没有转移就保留,但我们知道一定有整个字符串这一种方案(j=0),所以一定会被更新,所以可以初始化为LLONG_MINLLONG_MAX。 但是,j=0时,最后一段就是整个字符串,只要整个字符串合法(即第一个字符不是0,或者长度为1且不是0?但题目要求正整数,所以整个字符串一定合法?因为输入是正整数,所以字符串第一个字符不是0(除非是0,但题目数据范围最小为1),所以j=0一定合法?不一定,如果字符串是"0",那么不合法。但是题目数据范围最小为1,所以输入字符串第一个字符不是0。因此,j=0一定合法。 所以,我们可以在循环j=0时,直接赋值,然后j>0时再更新。 但是,为了代码统一,我们按动态规划的方式。 代码步骤: 1. 读入字符串s,长度len。 2. 定义n=len。 3. 初始化四个数组:dp_count, dp_sum, dp_max, dp_min,长度为n+1。 4. dp_count[0]=1, dp_sum[0
11-17
#include "stm32f10x.h" // Device header #include "Delay.h" #include "Key.h" #include "Led.h" #include "serial.h" #include "OLED.h" #include "Time.h" #include "Stack.h" static unsigned char Clear_Index=0; //清零检索 static unsigned char Count_Index=0; //计算检索 static uint32_t Index=0; static unsigned char Firmula[100]; //存储算数式子 static unsigned int Result; //存储结果 void USART1_IRQHandler(void); unsigned char a=19; int main(void) { Key_Init(); OLED_Init(); Serial_Init(); while(1) { if(Key_GetNum1()) //计算算数式 { Result=Deposit(Firmula); Count_Index=1; } if(Count_Index) //发送结果 { if(Key_GetNum2()) { printf("结果=%d",Result); OLED_ShowNum(1,1,Result,4); Index=0; Clear_Index=1; Count_Index=0; } } if(Clear_Index) //清零 { if(Key_GetNum3()) { Clear_Index=0; Init(); } } else if(!Clear_Index) { if(Key_GetNum3()) { printf("请输入运算式"); } } } } void USART1_IRQHandler(void) //串口中断函数 { Firmula[Index]=Serial_Getbyte(); printf("%c",Firmula[Index]); Index++; }#include "stm32f10x.h"// Device header #include "Stack.h" #include<ctype.h> #include "Serial.h" Stack_char Stack_CHAR; Stack_num Stack_NUM; uint8_t Push_char(Stack_char *stack,uint8_t CH); uint8_t Pop_char(Stack_char *stack,uint8_t *c); uint8_t Push_num(Stack_num *stack,unsigned int NUM); uint8_t Pop_num(Stack_num *stack,unsigned int *n); void Eval(void); uint16_t Priority(uint8_t ch); void Init(void) { Stack_NUM.top=0; Stack_CHAR.top=0; } uint16_t Priority(uint8_t ch) { switch(ch) { case '(' : case ')' : return 3; case '*' : case '/' : return 2; case '+' : case '-' : return 1; default : return 0; //分化优先级 } } uint32_t Deposit(uint8_t *String) { unsigned int i,j,index=0; uint8_t C; Init(); for(i = 0;String[i]!='\0'&&i < Stack_Size ;i++) { if(isdigit(String[i])) //判断是否 '0'<=string<='9' { index=0; j=i; for(;isdigit(String[j])&&j< Stack_Size;j++) { index=index*10+(String[j]-'0'); } Push_num(&Stack_NUM,index); i=j-1; //因为for循环多加了1,所以减去1 } else if(String[i]=='(') { Push_char(&Stack_CHAR,String[i]); } else if(String[i]==')') { while(Stack_CHAR.ch[Stack_CHAR.top] != '(') {Eval();} //直到遇到左括号,并且计算 if(Stack_CHAR.top != 0 && Stack_CHAR.ch[Stack_CHAR.top] == '(') { Pop_char(&Stack_CHAR,&C); //弹出左括号 } } else { while(Stack_CHAR.top!=0&&Stack_CHAR.ch[Stack_CHAR.top]!='('&&Priority(Stack_CHAR.ch[Stack_CHAR.top])>=Priority(String[i])) { Eval(); } Push_char(&Stack_CHAR,String[i]); } } while(Stack_CHAR.top) { Eval(); } //循环直至操作符为空 return Stack_NUM.num[Stack_NUM.top]; //此时数栈顶元素即为表达式值 } void Eval(void) { uint32_t a,x,b; uint8_t cha; Pop_num(&Stack_NUM,&b); Pop_num(&Stack_NUM,&a); //由于栈是陷进后出,与队列有区别(先进出) Pop_char(&Stack_CHAR,&cha); switch(cha) { case '*' : x=a*b;break; //计算 case '/' : { if(b==0) {printf("除数不能为0");x=0;} else {x=a/b;} break; } case '+' : {x=a+b;break;} case '-' : {x=a-b;break;} default :break; } Push_num(&Stack_NUM,x); } uint8_t Push_char(Stack_char *stack,uint8_t CH) { if(stack->top>=Stack_Size) { return 0; } stack->top++; stack->ch[stack->top]=CH; return 1; } uint8_t Push_num(Stack_num *stack,unsigned int NUM) { if(stack->top>=Stack_Size) { return 0; } stack->top++; stack->num[stack->top]=NUM; return 1; } uint8_t Pop_char(Stack_char *stack,uint8_t *c) { if(stack->top<=0) { return 0; } *c=stack->ch[stack->top]; stack->top--; return 1; } uint8_t Pop_num(Stack_num *stack,unsigned int *n) { if(stack->top<=0) { return 0; } *n=stack->num[stack->top]; stack->top--; return 1; } /* Copyright (C) ARM Ltd., 1999,2014 */ /* All rights reserved */ /* * RCS $Revision$ * Checkin $Date$ * Revising $Author: agrant $ */ #ifndef __stdint_h #define __stdint_h #define __ARMCLIB_VERSION 5060034 #ifdef __INT64_TYPE__ /* armclang predefines '__INT64_TYPE__' and '__INT64_C_SUFFIX__' */ #define __INT64 __INT64_TYPE__ #else /* armcc has builtin '__int64' which can be used in --strict mode */ #define __INT64 __int64 #define __INT64_C_SUFFIX__ ll #endif #define __PASTE2(x, y) x ## y #define __PASTE(x, y) __PASTE2(x, y) #define __INT64_C(x) __ESCAPE__(__PASTE(x, __INT64_C_SUFFIX__)) #define __UINT64_C(x) __ESCAPE__(__PASTE(x ## u, __INT64_C_SUFFIX__)) #if defined(__clang__) || (defined(__ARMCC_VERSION) && !defined(__STRICT_ANSI__)) /* armclang and non-strict armcc allow 'long long' in system headers */ #define __LONGLONG long long #else /* strict armcc has '__int64' */ #define __LONGLONG __int64 #endif #ifndef __STDINT_DECLS #define __STDINT_DECLS #undef __CLIBNS #ifdef __cplusplus namespace std { #define __CLIBNS std:: extern "C" { #else #define __CLIBNS #endif /* __cplusplus */ /* * 'signed' is redundant below, except for 'signed char' and if * the typedef is used to declare a bitfield. */ /* 7.18.1.1 */ /* exact-width signed integer types */ typedef signed char int8_t; typedef signed short int int16_t; typedef signed int int32_t; typedef signed __INT64 int64_t; /* exact-width unsigned integer types */ typedef unsigned char uint8_t; typedef unsigned short int uint16_t; typedef unsigned int uint32_t; typedef unsigned __INT64 uint64_t; /* 7.18.1.2 */ /* smallest type of at least n bits */ /* minimum-width signed integer types */ typedef signed char int_least8_t; typedef signed short int int_least16_t; typedef signed int int_least32_t; typedef signed __INT64 int_least64_t; /* minimum-width unsigned integer types */ typedef unsigned char uint_least8_t; typedef unsigned short int uint_least16_t; typedef unsigned int uint_least32_t; typedef unsigned __INT64 uint_least64_t; /* 7.18.1.3 */ /* fastest minimum-width signed integer types */ typedef signed int int_fast8_t; typedef signed int int_fast16_t; typedef signed int int_fast32_t; typedef signed __INT64 int_fast64_t; /* fastest minimum-width unsigned integer types */ typedef unsigned int uint_fast8_t; typedef unsigned int uint_fast16_t; typedef unsigned int uint_fast32_t; typedef unsigned __INT64 uint_fast64_t; /* 7.18.1.4 integer types capable of holding object pointers */ #if __sizeof_ptr == 8 typedef signed __INT64 intptr_t; typedef unsigned __INT64 uintptr_t; #else typedef signed int intptr_t; typedef unsigned int uintptr_t; #endif /* 7.18.1.5 greatest-width integer types */ typedef signed __LONGLONG intmax_t; typedef unsigned __LONGLONG uintmax_t; #if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) /* 7.18.2.1 */ /* minimum values of exact-width signed integer types */ #define INT8_MIN -128 #define INT16_MIN -32768 #define INT32_MIN (~0x7fffffff) /* -2147483648 is unsigned */ #define INT64_MIN __INT64_C(~0x7fffffffffffffff) /* -9223372036854775808 is unsigned */ /* maximum values of exact-width signed integer types */ #define INT8_MAX 127 #define INT16_MAX 32767 #define INT32_MAX 2147483647 #define INT64_MAX __INT64_C(9223372036854775807) /* maximum values of exact-width unsigned integer types */ #define UINT8_MAX 255 #define UINT16_MAX 65535 #define UINT32_MAX 4294967295u #define UINT64_MAX __UINT64_C(18446744073709551615) /* 7.18.2.2 */ /* minimum values of minimum-width signed integer types */ #define INT_LEAST8_MIN -128 #define INT_LEAST16_MIN -32768 #define INT_LEAST32_MIN (~0x7fffffff) #define INT_LEAST64_MIN __INT64_C(~0x7fffffffffffffff) /* maximum values of minimum-width signed integer types */ #define INT_LEAST8_MAX 127 #define INT_LEAST16_MAX 32767 #define INT_LEAST32_MAX 2147483647 #define INT_LEAST64_MAX __INT64_C(9223372036854775807) /* maximum values of minimum-width unsigned integer types */ #define UINT_LEAST8_MAX 255 #define UINT_LEAST16_MAX 65535 #define UINT_LEAST32_MAX 4294967295u #define UINT_LEAST64_MAX __UINT64_C(18446744073709551615) /* 7.18.2.3 */ /* minimum values of fastest minimum-width signed integer types */ #define INT_FAST8_MIN (~0x7fffffff) #define INT_FAST16_MIN (~0x7fffffff) #define INT_FAST32_MIN (~0x7fffffff) #define INT_FAST64_MIN __INT64_C(~0x7fffffffffffffff) /* maximum values of fastest minimum-width signed integer types */ #define INT_FAST8_MAX 2147483647 #define INT_FAST16_MAX 2147483647 #define INT_FAST32_MAX 2147483647 #define INT_FAST64_MAX __INT64_C(9223372036854775807) /* maximum values of fastest minimum-width unsigned integer types */ #define UINT_FAST8_MAX 4294967295u #define UINT_FAST16_MAX 4294967295u #define UINT_FAST32_MAX 4294967295u #define UINT_FAST64_MAX __UINT64_C(18446744073709551615) /* 7.18.2.4 */ /* minimum value of pointer-holding signed integer type */ #if __sizeof_ptr == 8 #define INTPTR_MIN INT64_MIN #else #define INTPTR_MIN INT32_MIN #endif /* maximum value of pointer-holding signed integer type */ #if __sizeof_ptr == 8 #define INTPTR_MAX INT64_MAX #else #define INTPTR_MAX INT32_MAX #endif /* maximum value of pointer-holding unsigned integer type */ #if __sizeof_ptr == 8 #define UINTPTR_MAX UINT64_MAX #else #define UINTPTR_MAX UINT32_MAX #endif /* 7.18.2.5 */ /* minimum value of greatest-width signed integer type */ #define INTMAX_MIN __ESCAPE__(~0x7fffffffffffffffll) /* maximum value of greatest-width signed integer type */ #define INTMAX_MAX __ESCAPE__(9223372036854775807ll) /* maximum value of greatest-width unsigned integer type */ #define UINTMAX_MAX __ESCAPE__(18446744073709551615ull) /* 7.18.3 */ /* limits of ptrdiff_t */ #if __sizeof_ptr == 8 #define PTRDIFF_MIN INT64_MIN #define PTRDIFF_MAX INT64_MAX #else #define PTRDIFF_MIN INT32_MIN #define PTRDIFF_MAX INT32_MAX #endif /* limits of sig_atomic_t */ #define SIG_ATOMIC_MIN (~0x7fffffff) #define SIG_ATOMIC_MAX 2147483647 /* limit of size_t */ #if __sizeof_ptr == 8 #define SIZE_MAX UINT64_MAX #else #define SIZE_MAX UINT32_MAX #endif /* limits of wchar_t */ /* NB we have to undef and redef because they're defined in both * stdint.h and wchar.h */ #undef WCHAR_MIN #undef WCHAR_MAX #if defined(__WCHAR32) || (defined(__ARM_SIZEOF_WCHAR_T) && __ARM_SIZEOF_WCHAR_T == 4) #define WCHAR_MIN 0 #define WCHAR_MAX 0xffffffffU #else #define WCHAR_MIN 0 #define WCHAR_MAX 65535 #endif /* limits of wint_t */ #define WINT_MIN (~0x7fffffff) #define WINT_MAX 2147483647 #endif /* __STDC_LIMIT_MACROS */ #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) /* 7.18.4.1 macros for minimum-width integer constants */ #define INT8_C(x) (x) #define INT16_C(x) (x) #define INT32_C(x) (x) #define INT64_C(x) __INT64_C(x) #define UINT8_C(x) (x ## u) #define UINT16_C(x) (x ## u) #define UINT32_C(x) (x ## u) #define UINT64_C(x) __UINT64_C(x) /* 7.18.4.2 macros for greatest-width integer constants */ #define INTMAX_C(x) __ESCAPE__(x ## ll) #define UINTMAX_C(x) __ESCAPE__(x ## ull) #endif /* __STDC_CONSTANT_MACROS */ #ifdef __cplusplus } /* extern "C" */ } /* namespace std */ #endif /* __cplusplus */ #endif /* __STDINT_DECLS */ #ifdef __cplusplus #ifndef __STDINT_NO_EXPORTS using ::std::int8_t; using ::std::int16_t; using ::std::int32_t; using ::std::int64_t; using ::std::uint8_t; using ::std::uint16_t; using ::std::uint32_t; using ::std::uint64_t; using ::std::int_least8_t; using ::std::int_least16_t; using ::std::int_least32_t; using ::std::int_least64_t; using ::std::uint_least8_t; using ::std::uint_least16_t; using ::std::uint_least32_t; using ::std::uint_least64_t; using ::std::int_fast8_t; using ::std::int_fast16_t; using ::std::int_fast32_t; using ::std::int_fast64_t; using ::std::uint_fast8_t; using ::std::uint_fast16_t; using ::std::uint_fast32_t; using ::std::uint_fast64_t; using ::std::intptr_t; using ::std::uintptr_t; using ::std::intmax_t; using ::std::uintmax_t; #endif #endif /* __cplusplus */ #undef __INT64 #undef __LONGLONG #endif /* __stdint_h */ /* end of stdint.h */ 在不改变原代码变量名情况下,实现小数点预算
07-16
分析下面两组代码的不同 代码1:long long count(char* str) { long long number[MAX] = { 0 }; char symbol[MAX] = { 0 }; int num_top; int sym_top; int i; long long a; long long b; char op; num_top = -1; sym_top = -1; i = 0; for (i = 0;i < strlen(str);i++) { if (isdigit(str[i])) { long long num = 0; while (i < strlen(str) && isdigit(str[i])) { num = num * 10 + (str[i] - '0'); i++; } i--; number[++num_top] = num; } else if (str[i] == '(') { sym_top++; symbol[sym_top] = str[i]; } else if (str[i] == ')') { while (sym_top >= 0 && symbol[sym_top] != '(') { b = number[num_top--]; a = number[num_top--]; op = symbol[sym_top--]; num_top++; number[num_top] = apply(a, b, op); } sym_top--; } else { while (sym_top >= 0 && pre(str[i]) <= pre(symbol[sym_top])) { b = number[num_top--]; a = number[num_top--]; op = symbol[sym_top--]; num_top++; number[num_top] = apply(a, b, op); } /*sym_top++;*/ symbol[++sym_top] = str[i]; } } while (sym_top >= 0) { b = number[num_top--]; a = number[num_top--]; op = symbol[sym_top--]; num_top++; number[num_top] = apply(a, b, op); } return number[num_top]; } 代码2: int count(const char* str) { long long number[MAX] = { 0 }; int num_top; char symbol[MAX] = { 0 }; int sym_top; int i; long long a; long long b; char op; num_top = -1; sym_top = -1; for (i = 0;i < strlen(str);i++) { if (isdigit(str[i])) { long long num; num = 0; while (i < strlen(str) && isdigit(str[i])) { num = num * 10 + (str[i] - '0'); i++; } i--; num_top++; number[num_top] = num; } if (str[i] == '(') { symbol[++sym_top] = str[i]; }else if (str[i] == ')') { while (sym_top > -1 && symbol[sym_top] != '(') { b = number[num_top--]; a = number[num_top--]; op = symbol[sym_top--]; num_top++; number[num_top] = apply(a, b, op); } sym_top--; }else { while (sym_top > -1 && pre(str[i]) <= pre(symbol[sym_top])) { b = number[num_top--]; a = number[num_top--]; op = symbol[sym_top--]; num_top++; number[num_top] = apply(a, b, op); } symbol[++sym_top] = str[i]; } } while (sym_top > -1) { b = number[num_top--]; a = number[num_top--]; op = symbol[sym_top--]; num_top++; number[num_top] = apply(a, b, op); } return number[num_top]; }
03-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值