1. 程式人生 > >ACM PKU 1844 解題報告

ACM PKU 1844 解題報告

問題描述:Consider the natural numbers from 1 to N. By associating to each number a sign (+ or -) and calculating the value of this expression we obtain a sum S (0< S <= 100000). The problem is to determine for a given sum S the minimum number N for which we can obtain S by associating signs for all numbers between 1 to N. For a given S, find out the minimum value N in order to obtain S according to the conditions of the problem.

這個問題初一看,似乎是一道動態規劃題:對某一個 n,如果為 n 加上負號,則問題變成在 1 ~ n-1 中加上正負號使得其和為 S+n;如果為 n 加上正號,則子問題為 1 ~ n-1 中加上正負號使其和為 S-n。因此,可以記狀態 f(n,s) 表示能否用 1 到 n 這些數通過加上正負號得到和 s,則

      f(n,s) = f(n-1,s+n) and f(n-1,s-n);

      f(n,s) = false  if  n*(n+1)/2 < |s|

為了求解N,我們只需要從 1 開始列舉,直到第一個 N 使得 f(N,S)=true。時間複雜度是 O(N*S + N3

)。 N3 這個項是因為,狀態中 f(n,s) 的 s 最多隻有 s + n*(n+1)/2 那麼大(這是悲觀估計了,但複雜度的話應該是一樣的)

分析到這裡,似乎問題解決了,但其實不然:複雜度中的 N 是事先不知道的!更糟的是,狀態空間看起來未必是有限的!我們從 1 開始列舉 f(i,S),但我們會在哪裡結束呢?會不會對某個 S,不存在這樣的 N?即使對每個 S 都有解存在,那麼會不會 N 隨 S 的增長速度是指數級的,甚至更快?如果是這樣的話,對於較大的 S,這個方法就不現實了。

事實上,結果總算不是太壞:N =  O(S) !因此上述動態規劃演算法的時間複雜度是 O(S3 )。為什麼 N=O(S)?對於給定的 S,考慮 1~ 2S,給奇數加上負號,給偶數加上正號,其和正好是 S。因此, N <= 2S。對於這樣複雜度的演算法,想 AC POJ1844 估計還是有困難的(本人還沒試過,有空去試試~)

除了動態規劃,還可以考慮搜尋,但剪枝效果值得懷疑。本人不才,暫時沒想出很有效的剪枝,也懶得想了,有誰知道的請不吝留言相告。

事實上,我剛看到這道題目時,第一個想到的不是動態規劃,也不是搜尋,而是數學上的解析解。這題帶有太明顯的數論痕跡,不讓人想到數論都難啊。一開始想的是素因子:無論是正是負,本質上就是加上或減去某個素因子的倍數。然而,此路似乎走不通。於是轉而考慮奇偶性。事實證明,這是一條捷徑啊!

首先,N*(N+1)/2 >= S。這是顯然的。前 N 個數的和最大也就 N*(N+1)/2。如果 N*(N+1) < S,則 N 顯然不可能是解。如果等號成立,則 N 是解。現在假定 n 為使得 max = n*(n+1)/2 >= S 成立的最小整數。記 Q 為 1~n 對某些數新增負號後所得的和。

當 max 嚴格大於 S 時,直覺上,可以通過對其中的某些元素新增負號,即減去某些數,讓 Q 變小直到等於 S。考慮對 1~n 中某個數 x 新增負號,則 Q = max - 2*x。一個非常重要的觀察就是 Q 的奇偶性和 max 是一樣的!簡單的歸納法表明 Q 和 max 的奇偶性始終保持一致。如果 max 和 S 的奇偶性不同,則 Q 不可能等於S,因而 n 顯然不會是解。

現在先假定 max 和 S 的奇偶性一樣。考慮 d = max - S。則 d 為偶數,於是  d/2 正好為整數。由於  n 是使得 n*(n+1)/2 >= S 的最小整數,則 d < n,於是 d/2 < n。因此,只需要把 d/2 這個數變成負的就可以使得 Q=S。同時,由於 n 的最小性,n 為解 N。

最後考慮 max 和 S 的奇偶不一樣。於是 n 不是解。考慮 n+1。如果 n+1 是奇數,則 max 的奇偶性發生變化,並且和 S 一樣,於是根據上述論述,N=n+1;如果  n+1 是偶數,則 max 的奇偶性不變,n+1 也不可能是解,但 n+2 是奇數,於是,N=n+2。

時間和空間複雜度呢?都為 O(1) !至此,問題得到近乎完美的解答。根據這個方法也出的程式碼,也是相當的短小。由於程式碼很短,我就用程式碼代替虛擬碼了

後記:數學真強大啊!用常數時間解決了一個看似複雜度很高的問題!後來在該題目的討論區也看到了幾乎相同思路的短程式碼。那個程式碼非常短,但犧牲了速度:從 1 開始累加,直到遇到第一個 n,使得 d=max-S 大於 0 且其為偶數。其時間複雜為 O(N),或者說 O( S1/2 )。個人還是比較喜歡複雜度低的程式碼,雖然優雅程度不足,但卻是最優的。