洛谷-P1249 最大乘積

題目描述

一個正整數一般可以分爲幾個互不相同的自然數的和,如 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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章