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(2m))的時候,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
表明(2i+n-1)n/2 = m (公式1)
那麼 i=((2m)/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=12,1 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;
}