洛谷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;
}

这题还有一种做法,就是将其取对数化为背包问题,但是我还没学,以后再说吧

posted @ 2023-04-18 16:09  凪风sama  阅读(51)  评论(0编辑  收藏  举报