HDU 2058The sum problem(詳細解答原理!!!

The sum problem

Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 39373 Accepted Submission(s): 11831

Problem Description
Given a sequence 1,2,3,…N, your job is to calculate all the possible sub-sequences that the sum of the sub-sequence is M.

Input
Input contains multiple test cases. each case contains two integers N, M( 1 <= N, M <= 1000000000).input ends with N = M = 0.

Output
For each test case, print all the possible sub-sequence that its sum is M.The format is show in the sample below.print a blank line after each test case.

Sample Input

20 10
50 30
0 0

Sample Output

[1,4]
[10,10]

[4,8]
[6,9]
[9,11]
[30,30]

首先千萬不要使用二重循環,你已提交保證超時,無論你怎麼改還是超時,本人親自體驗過!

這道題使用的是數學知識
但是很多博主講的也不詳細,晦澀難懂!因爲他們用的標識符自己懂就ok,標識符也未必知名其意
這裏我詳細講解一下

首先輸入N,M
初看題目可能存在M<=N,M>=N但N數和小於M,或者M>=N但N 數和大於M總共三種情況!

但是下面的講解的都會把這幾種情況包括進來(下面講解時n表示N,m表示M)

1 2 3 4 5 6…N

等差數列n項和公式爲 n* a1+n*(n-1)/2 (公式0)

假如從1開始,n項和爲n+n*(n-1)/2 = n*(n+1)/2

令n*(n+1)/2=m n*(n+1)=2m

n = sqrt(2m) 先假如n是浮點數,那麼 (sqrt(2m))(sqrt(2m)+1)>2m
sqrt(2m) * sqrt(2m)<2m
那麼n是整數時(即 n=(int)sqrt(2
m))的時候,n n<=2m 是絕對小於2m的
爲什麼可以等於呢?
比如m=10,n=4,剛好歪打正着唄

那麼求出來的n是什麼意思呢?
假如m=10,此時n=4
n是1 2 3 4 這四個數是最接近10的,(該例子剛好等於10,可以自己嘗試m=12時的情況)

其實n也可以理解爲子序列最大的項數,其中1…n是最小的四項元素數,因爲剛好從1開始算起

現在說明另一個公式
*子序列元素和 (首元素+末元素)項數個數/2

那麼對於現在該序列首元素也可以說首位置吧(從1開始,不是從0開始哦!)

現在i作爲首元素,項數爲n
那麼有(i + i+n-1)*n /2
比如 1 2 3 4 =10 或者 4 5 6 7 8 =30
自己將i=1,n=4代入和i=4,n=5代入加深理解

既然子序列和等於(2*i+n-1)n/2
表明(2
i+n-1)n/2 = m (公式1)
那麼 i=((2
m)/n -n +1)/2 (公式2)(公式1反推)

現在看代碼就更容易理解我所講的,我會每句都會講解的,要耐心看!一定要把它弄懂

#include<iostream>
#include<cmath>
using namespace std;
//(i+i+n-1)*n/2=m;
//i=(2*m/n-n+1)/2
int main()
{
	int N,M;
	while(cin>>N>>M&&(N||M))
	{
		int len = (int)sqrt(2*M);
len其實就是最大的項數(即子序列個數的最大值),你想想

我從1開始不斷的+2+3+....k,假如剛好k位置時即從1到k的所有元素和加起來剛好
小於M,在1到k+1就大於M,那麼len就是子序列個數的最大值,不可能還超過len的,
既然len是最大的子序列個數,但是從1到len的這幾位數加起來是最小的,
可能小於M(比如m=12的時候,此時就需要往前移,比如 2 3 4 5 ,若還小於
那就繼續移 3 4 5 6 
(**在這個移的過程中就相當於在找首元素 i ,下面解釋如何找i,肯定不是循環)**。
可能移某位置大於m的時候還是不滿足(比如m=121 2 3 4 是小於m的,
2 3 4 5是大於m的)那麼接下來就是在三個子序列數(子序列個數爲3)來找
因爲四個是不可能的,因爲越往後越大,當進行三個子序列找
的時候,就需要找首元素,一旦首元素找到,後面的元素就立馬確定
當然不是循環來找,那估計可能又會超時!!!嘿嘿

		int n,i;//n就是子序列個數,i是首元素
		for(n=len;n>=1;n--)//子序列個數不斷減少直到1
		{我可以直接說很多用到這個循環到1的都是這個原理
//既然子序列和等於(2*i+n-1)*n/2
//表明(2*i+n-1)*n/2 = m   (公式1)
//那麼 i=((2*m)/n -n +1)/2    (公式2)

			i=(2*M/n-n+1)/2;當然找這個首元素也可以多總找法
//根據公式2反推首元素(對於每個子序列個求i首元素)

			if((2*i+n-1)*n==2*M)這裏判斷相等也可以使用 公式0
			{
	//這裏爲什麼會判斷啦?
//因爲計算i的過程中並不是兩個整數除以整數等於整數,其實是有小數的
//只是int隱式轉化爲整數,如果兩次的整數都存在這種情況,說明這個求出來
//i再進行計算是不滿足公式1的,只有兩次除法的時候都是剛好沒有
//小數點(也就是兩個數除餘等於0)
				cout<<'['<<i<<','<<i+n-1<<']'<<endl;
				}
		}
		cout<<endl;
	}
	return 0;
}
最後,要自己拿數據嘗試,自己動手在紙上計算。我覺得我講的夠詳細啦!
我把我的理解講出來啦!我盡力啦,畢竟我不是老師,哈哈哈!!!

我覺我很囉嗦!但是又怕沒講清楚,希望對大家理解有幫助
總的來說,萬變不離其宗,重點是get到裏面的點就明白啦!

當然如果理想直到二重循環怎麼寫?
請參考下部分代碼

#include<iostream>
using namespace std;
int main()
{
	int N,M;
	while(cin>>N>>M&&(N||M))
	{
		int i,j,k;
		int value;
		int sum;
		for(i=1;i<=N;i++)
		{
			value=i;
			sum=0;
			for(j=value;j<=M&&j<=N;j++)
			{
				sum+=j;
				if(sum>M)
				{
					if(sum-j==M)
					{
						printf("[%d,",value);
						printf("%d]\n",j-1);
					}
				}
			}
		}
		if(M<=N)
			printf("[%d,%d]\n",M,M);
		printf("\n");
	}
	return 0;
}

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