洛谷P1249最大乘积,数论找规律
这道题是数论加高精度,高精度倒好说,就是高精度乘法实现,模拟列竖式乘法,但是找出要乘的这几个数实属不容易,没学过数论,只能从题解中学怎么找规律
这里引用一下洛谷题解区赞数最高的题解
不知道大家看其他的题解有没有产生很多问号??? (本题解修改了一些第一次提交发现的错误,感谢yhm12345同学指出。) 我来帮大家理清一下这题的思路,并对其他题解做出解释和补充(我之前看的时候也比较懵)。 以下是本题的主要思路: 大家都清楚这题的意思,我们尽可能的把n分成更多份(都大于1)那样乘积最大。 (这里讲一下如果分出来的数可以重复,那么有时候并不是分出的份数越多越大,比如6,分成2,2,2不如分成3,3。但是不能重复的话就不存在这样的情况,大家可以想一下。) 以“6”为例,我们可以想到,我们先分出来一个2,然后再分出一个3......但是这样面临一个问题:“最后有余数怎么办”,比如“8”;分出来2,3还剩下3,没法再分出4,这时候怎么办呢?有的同学会想我把余数3都加到3身上,我们就分成了2和6,但是这样是最大的吗?(3和5才是最大的) 这时候我补充一下有一篇题解中所说: “把余数分到大的数上比分到小的数上得到的乘积更大”。 实际不太准确,我们很容易证明出来如果能把数分到更小的数上,那么乘积更大(大家可以想想)。但是为什么最终是分到了大的数呢?就好比把6分成2,4,按照我们刚才的分法,先分出来了2和3最后余1,理论上我们把1给2得到的结果更大,但是我们不允许数重复,所以我们需要先把1给3,这样如果还剩下余数的话就分给2,所以当小的数被分配某个数后不会造成数的重复,那么优先给小的数分配,所以就像其他题解所说: “从大数开始向前,依次分配1”。 所以对于8我们先分配出了2,3又余3,我们先分配1给3,得到2,4这时候2可以被分配,那么我们就分配给2一个1,得到3和4,这时候还余1,我们就分配给4,得到3,5。 这里我来解释一下点赞数最多的题解的思路。 以下为引用部分: 本题要先用简单的数论和贪心找到最优解的组成方法,再用高精度乘法求积。 以2004为例,由于把2004分拆成若干个互不相等的自然数的和的分法只有有限种,因而一定存在一种分法,使得这些自然数的乘积最大。 若1作因数,则显然乘积不会最大。把2004分拆成若干个互不相等的自然数的和,因数个数越多,乘积越大。为了使因数个数尽可能地多,我们把2004分成2+3…+n直到和大于等于2004。 若和比2004大1,则因数个数至少减少1个,为了使乘积最大,应去掉最小的2,并将最后一个数(最大)加上1。 若和比2004大k(k≠1),则去掉等于k的那个数,便可使乘积最大。 例如15:s=2+3+4+5+6刚好大于15,s-15=5,所以把5去掉。 又例如13:s=2+3+4+5刚好大于13,s-13=1,所以去掉2,并把5加1,即3 4 6。 大家可能觉得这个和我们刚才所讲的不太一样,为什么要减呢?怎么想到的呢?显然这样减,比我们一个个循环加更快。 我来帮大家推导一下: 我们分出来了2,3,4......n这n-1个数然后余数是K(1<=K<=n)。 如果K==n,我们需要进行两轮分配,意思是从n分配到2还剩下1,需要再回去把1分配给最大的数(也就是n+1)最终得到3,4,5......n+2这也就应对了上文题解中的情况2; 如果K<n,我们进行一轮分配就好因为我们的余数是K,那么分配到n+1-K就停止了,拿15举例,先分配出了2,3,4,5余1,我们分配到5就停止了,(n+1-K=5)。 对于上篇题解他先分配出2,3,4......n,n+1。因为我们分出来n-1个数余数是K,而分配出n+1后会造成数不够,还差n+1-K,那么这也就对应前两行我们推导出的结果,去掉n+1-K,就相当于分配到了n+1-K,应为n+1-K经过分配后变成了,n+2-K,这不就相当于去掉了吗。所以这相当于找规律了。
可以预知,在不允许重复的前提下一个数字分的项数越多(1除外,乘法中1不做贡献)(一般),其乘积一般会越大,因此我们期望将一个数从2连续分,直到分完整个数为止,显然许多数并不满足此情况,对于无法正好分完的数,我们可以将其分成两种情况
对于数 N,从2开始2,3,4....n-1,n,k,保证从2到n是连续数字,最后余数为k,很显然1<=k<=n,对于k,我们考虑将其均分在2-n的数里
具体分法以及解释可以参考上述题解的描述
当k==n时,从2-n这n-1个数,都可以分到一个1,最终剩下n-n-1=1,再重复这个步骤,正好将这多的1分到最后一个数上
可以参考如图
以下是AC代码
#include<iostream> using namespace std; int Num[10001]; int main() { int n; cin >> n; int Sum = 0; int Cnt = 0; //if (n < 5) //{ // if (n == 3) // printf("1 2\n2"); // if (n == 4) // printf("1 3\n3"); //} // else { for (int i = 2;; i++) { Sum += i; Num[Cnt++] = i; if (Sum == n) break; else if (Sum > n) { if (Sum == n + 1) { Num[0] = 1; Num[Cnt - 1] += 1; } else { Num[Sum - n - 2] = 1; } break; } } //以上是找数字部分,下面是高精度乘法部分 int Anwser[500000] = { 0 }; Anwser[0] = Num[0]; int Digit = 1; int Last = 0; for (int i = 1; i < Cnt; i++) { for (int j = 0; j < Digit; j++) { int Temp = Anwser[j] * Num[i] + Last; Anwser[j] = Temp % 10; Last = Temp / 10; } while (Last) { Anwser[Digit++] = Last % 10; Last /= 10; } } int k = 0; while (Num[k] == 1) k++; cout << Num[k++]; for (; k < Cnt; k++) if(Num[k]!=1) cout << ' ' << Num[k]; cout << endl; for (int l = Digit - 1; l >= 0; l--) { cout << Anwser[l]; } } return 0; }
这题还有一种做法,就是将其取对数化为背包问题,但是我还没学,以后再说吧