題目描述
一個正整數一般可以分爲幾個互不相同的自然數的和,如 3=1+23=1+2,4=1+34=1+3,5=1+4=2+35=1+4=2+3,6=1+5=2+46=1+5=2+4。
現在你的任務是將指定的正整數 nn 分解成若干個互不相同的自然數的和,且使這些自然數的乘積最大。
輸入格式
只一個正整數 nn,(3 \leq n \leq 100003≤n≤10000)。
輸出格式
第一行是分解方案,相鄰的數之間用一個空格分開,並且按由小到大的順序。
第二行是最大的乘積。
輸入輸出樣例
輸入 #1複製
10
輸出 #1複製
2 3 5 30
分析:
這題博主不會,我大致能想到如果允許重複的話,那麼對半分應該乘積是最大的,但是不允許重複,真的沒有什麼思路
看看題解吧
本題要先用簡單的數論和貪心找到最優解的組成方法,再用高精度乘法求積。
以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。
高精度乘法我理解,最後可能會爆int,但是這個找的方法就很神奇,不知道爲什麼會這樣想,往下翻翻,找到了其他題主的解釋:
以下是本題的主要思路:
大家都清楚這題的意思,我們儘可能的把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,這不就相當於去掉了嗎。所以這相當於找規律了。
基本思想就是,對於一個數S,先用2+3+4+....+n的方法,找到和S臨界的那個n,如果這一串數字剛好等於S,那麼顯而易見,答案就是2*3*4*...*n,但是情況並不總是這樣
如果這一串數字的和比S大呢,假設大K,即餘數爲K,那麼又該如何?
如果這個餘數K==最後一個數字n,那麼顯而易見,給這一組數(n-1個數),每人分配一個1,就會變成3+4+5+...+n+(n+1),那麼還多一個1,怎麼分呢,總不能就補一個1上去吧,變成1+3+4+5+...+n+(n+1),這樣分是可以,但是乘積並不是最大,爲什麼?因爲1乘任何數都沒有變化,反而白白浪費了1這個數。那加到最小的3上面?如果允許重複,那麼加到3上面會讓乘積最大(因爲3最小,讓最小的數變大,總乘積會最大化,很簡單就可以驗證),但是很遺憾不允許重複,那麼就只有加到n+1身上了,變成n+2,即式子變成3+4+5+...+n+(n+2)
如果餘數K<最後一個數字n呢,那就把2+3+4+....+n這組數從最後一個n開始,每個數加1,直到K分配完了爲止,最後就會變成2+3+4+...+m+(m+2)+(m+3)+....+n+(n+1)+(n+2),請注意,中間斷開了,沒有m+1這一項,因爲K分配到m+1之前,就已經用完了,這就是使乘積最大的序列了,有人會問,這和那個答主給的答案有什麼關係呢?大家看我分析
按照答主的方式,直接找到2+3+4+5+....+n+(n+1)+(n+2),使得這一串數字大於S(注意是大於,前面分析的都是不大於),然後呢,大了K,那麼怎麼辦,去掉其中值爲K的那個數,而那個數在哪呢,如果你仔細分析,就會發現就是上一段中斷開的m+1,即從這一串2+3+4+5+....+n+(n+1)+(n+2)挖去m+1,變成2+3+4+...+m+(m+2)+(m+3)+....+n+(n+1)+(n+2),和上面一模一樣。
其他的不多說了,上代碼吧
#include<iostream>
#include<string>
#include<vector>
using namespace std;
string add(string str1,string str2);
string mul(string str1,string str2);
string calc();
vector<int> list;
string ans;
int main()
{
int n;
cin>>n;
int sum=0;
for(int i=2;sum<n;i++)
{
list.push_back(i);
sum+=i;
}
if(sum==n)//恰好多項式==n
{
ans=calc();
}
else if(sum>n)//多項式>n
{
int k=sum-n;
if(k==1)//剛好差1,捨棄2,最大位+1
{
list.erase(list.begin(),list.begin()+1);
list[list.size()-1]+=1;
ans=calc();
}
else//否則刪除list中=k的那一項
{
vector<int>::iterator it;
int flag=0;
for(it=list.begin();it<list.end();it++)
{
if(*it==k)
{
list.erase(it,it+1);
flag=1;
}
if(flag)
break;
}
ans=calc();
}
}
cout<<ans;
return 0;
}
string add(string str1,string str2)
{
if(str1.length()<str2.length())
{
string s;
s=str1;
str1=str2;
str2=s;
}
string str;
int len1=str1.length();
int len2=str2.length();
int dis=len1-len2;
int cf=0;
for(int i=len2-1;i>=0;i--)
{
int temp=str1[i+dis]-'0'+str2[i]-'0'+cf;
cf=temp/10;
temp=temp%10;
str=char(temp+'0')+str;
//cout<<"cf is "<<cf<<" temp is "<<temp<<endl;
}
for(int i=dis-1;i>=0;i--)
{
int temp=str1[i]-'0'+cf;
cf=temp/10;
temp=temp%10;
str=char(temp+'0')+str;
}
if(cf>0)
str=char(cf+'0')+str;
if(str.find_first_not_of('0')==str.npos)
str="0";
else
str.erase(0,str.find_first_not_of('0'));
return str;
}
string mul(string str1,string str2)
{
if(str1.length()<str2.length())
{
string s;
s=str1;
str1=str2;
str2=s;
}
string str="0";
string tempstr;
int len1=str1.length();
int len2=str2.length();
int dis=len1-len2;
int cf=0;
for(int i=len2-1;i>=0;i--)
{
tempstr.clear();
cf=0;
for(int j=len1-1;j>=0;j--)
{
int temp=(str1[j]-'0')*(str2[i]-'0')+cf;
//cout<<"temp is "<<temp<<endl;
cf=temp/10;
//cout<<"cf is "<<cf<<endl;
temp=temp%10;
tempstr=char(temp+'0')+tempstr;
//cout<<endl;
}
if(cf>0)
tempstr=char(cf+'0')+tempstr;
for(int k=1;k<=len2-1-i;k++)
tempstr=tempstr+'0';
if(tempstr.find_first_not_of('0')==tempstr.npos)
tempstr="0";
else
tempstr.erase(0,tempstr.find_first_not_of('0'));
//cout<<"tempstr is "<<tempstr<<endl;
str=add(str,tempstr);
//cout<<"str is "<<str<<endl;
}
if(str.find_first_not_of('0')==str.npos)
str="0";
else
str.erase(0,str.find_first_not_of('0'));
return str;
}
string calc()
{
int flag=0;
vector<int>::iterator it1;
for(it1=list.begin();it1<list.end();it1++)
{
if(flag==0)
{
cout<<*it1;
flag=1;
}
else
cout<<" "<<*it1;
}
cout<<endl;
vector<int>::iterator it;
it=list.begin();
string s1;
s1=char(*it+'0')+s1;
//cout<<"s1: "<<s1<<endl;
for(it=list.begin()+1;it<list.end();it++)
{
string s2;
int temp=*it;
while(temp)//當*it爲兩位,即12,13..時,無法通過 s2=char(*it+'0')+s2;一次獲取s2了
{
int tempyu=temp%10;
s2=char(tempyu+'0')+s2;
temp=temp/10;
}
//cout<<"s2: "<<s2<<endl;
s1=mul(s1,s2);
//cout<<"s2 is "<<s2<<" s1: "<<s1<<endl;
}
return s1;
}