Two Sets II
題目
給一個數 ,問你有幾種方法將 的數分成兩堆,且兩堆總和相同,由於答案可能很大,請對 取餘數。
輸入
共一個數 。
輸出
共一個數,代表有多少種方法。
範例測資
Input:
7
Output:
4
想法
先簡化一下題目,我們要用一些數湊出某個總和,而這個總和會是 ,如果他不是整數,代表兩堆湊不出相同的值,答案是 0 。
否則,問題變成有 的數各一個,問有幾種做法能湊出 。 首先可以枚舉所有拿法,最簡單就是每個數選擇拿或不拿,但是這樣的時間複雜度是 。
但是這樣太慢了,我們又希望能夠更快,就代表我們需要改變枚舉的方法。
現在,我們考慮某一個物品、要不要放,得到的結果是特定總和的答案。 那我們可以調換一下變因,改成考慮某個物品、以及現在的總和,聽起來可能很抽象,但這件事就像是值域二分搜,換個角度看會有完全不同的複雜度。
假設 代表的是選擇 間的數,重量恰好為 的選擇方法,也就是我們要的答案。 可知 ,也就是不取這個數的方法數,和取了這個數之後的方法數的總和就是答案,因此這是二維 dp ,並且由於他是考慮一個數可以選或不選,用枚舉值域的方式降低複雜度,所以是時間複雜度 的 01 背包。
但其實,空間和時間上都能再優化,考慮剛剛的轉移式,會發現現在會用到的只有上一個物品的狀態,可以用滾動陣列做,附在範例程式碼二。 再仔細觀察,會用到的只有總和比現在小的狀態,因此從總和大的往小的做,就可以壓在一條陣列裡,配合接下來的時 間優化一起附在範例程式碼三。
時間上,不需要跑過整個值域,假設現在正在選擇 ,只要考慮到 的總和,也就是 ,最終的總複雜度是 ,而原本的 略大一點,不過這題不卡常數,三個版本都能過,但類似的觀察有時候會有神奇的效果,甚至可能降低複雜度,像是均攤分析、離散化、或 dp 優化,總物品有限所以複雜度並沒有想像中的高。
這題還有一個特別的地方,這樣 dp 算出來的答案是所有總和為 的方法數,但本題把左右兩堆交換視為一樣的,所以答案要除以二,而因為要對 取餘數,最終的答案應該是一個整數才對,若答案是奇數就會有問題,因此在取模的情況下,除法的定義變得有些不同,我們可以將 表示為 ,他會滿足 ,而費馬小定理告訴我們當 是質數時, ,而這樣的 便是乘法中的反元素,被我們簡稱為模反元素,可以透過快速冪算出答案,附在範例程式碼一。