You've got a positive integer sequence a1, a2, ..., an. All numbers in the sequence are distinct. Let's fix the set of variables b1, b2, ..., bm. Initially each variablebi(1 ≤ i ≤ m) contains the value of zero. Consider the following sequence, consisting ofn operations.
有n個數, 作為一組等式的得數.
有m個變數, 可以視為容器.
The first operation is assigning the value of a1 to some variablebx(1 ≤ x ≤ m). Each of the followingn - 1 operations is assigning to some variableby the value that is equal to the sum of values that are stored in the variablesbi andbj(1 ≤ i, j, y ≤ m). At that, the value that is assigned on thet-th operation, must equal at. For each operation numbersy, i, j are chosen anew.
第一個變數首先賦值為a1, 接下來給其他的變數賦值(其他的變數可以是已經出現過的, 將覆蓋原值), 並且需要滿足第t個賦值語句的返回值與at.相等.
Your task is to find the minimum number of variables m, such that those variables can help you perform the described sequence of operations.
問題是找出最小的m. 即容器個數.
思路:
用範例類比了一下, 前幾個還好判斷, 到後面就需要看看再往後是否需要用到這個數來決定是否覆蓋...有點像背包問題, 要合理地調度前面的變數...以免出現誤刪. 同時又要保證總的變數數最少.
用dp.
dp記錄目前狀態下需要的最少變數數...
看題解:
http://blog.sina.com.cn/s/blog_6ffc3bde01017l92.html
想法: 很明顯是一個DP題,當看到n<=23時也很容易想到是數位DP,但是如何DP卻很讓人無從下手。一般DP題就是遞推推公式,而公式又是由一些很顯然的很關鍵的規律得到的。
我們可以從第一個數字開始掃,直到掃到最後一個數字,如何從當前數字掃到下一個數字是我們需要解決的問題。
我們可以將每個數字中的1理解為需要中繼的個數,遞迴時每次都去掉當前位置的1加上上一位置的1(站在上一狀態的角度想一想就可以理解為什麼要加上一位置的1了)和形成當前位置的數所需要的位置的1,用|(或運算)可以很巧妙得解決位置重複的問題。
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define INF 25int n,a[25],dp[1<<23];//m表示狀態一共23位. 從右往左數. 哪一位為1則表示至少需要多少個中繼.int fun(int m,int x) //掃描到第x位至少需要多少個中繼(x從0開始){ if(dp[m]) return dp[m]; int c = __builtin_popcount(m),v = 0,mn = INF;//c下一狀態需要我保證的中繼的個數 for(int i = 0; i < x; i ++) { for(int j = 0; j <= i; j ++) { if(a[i] + a[j] == a[x])//如果這兩個結果相加可以得到最終結果(儘可能靠前),則... {//應該觀察到, 每一個a[i]都對應一個變數b,因為結果要有容器. //每一個現存的容器都對應了一個a[i].容器是游離的,未知的,只當需要的時候將它計算進來. 每一步都如此, 就可以保證正確. v = fun((m&~(1<<x))|(1<<(x-1))|(1<<i)|(1<<j), x-1); //第一項表示消除當前變數;1<<(x-1)表示掃描到了前一位,那麼這前一位必然要佔一個變數. //後兩項就表示額外需要保留的中介項.(就這麼簡單?) mn = min(mn,max(v,c));//其實所有的數字都是由c得到的,v只作為中介,將不同的c作比較 } } } return dp[m] = mn;}int main(){ scanf("%d",&n); for(int i = 0; i < n; i ++) scanf("%d",&a[i]); dp[1] = 1;//初始條件為掃描到第一位..必然至少需要1個變數 int ans = fun(1<<(n-1),n-1); if(ans == 25) ans = -1; printf("%d\n",ans); return 0;}
感覺仍是非常難以理解啊......
主要是要培養另一種分析問題的角度.
在本題中,則是:
抓住核心:
每一個得數由兩個加數組成, 這兩個加數都是之前的某個得數.
如果就選擇這兩個得數作為加數, 那麼這兩個得數對應的容器就要保留下來作為中轉,而不能被覆蓋(複用).
而當前的這個得數需要使用的容器又可以由更靠後的需求決定...
對!還有一點!!容器的編號是不需要考慮的,只要記錄所需容器的最大個數,那麼就一定夠用啦.
m就是有用容器的保留...掃描到不同位時m的變化可看做變數的申請與釋放!
本題值得回味啊....
附popcount的一種二分實現...
unsigned popcount (unsigned u){ u = (u & 0x55555555) + ((u >> 1) & 0x55555555); u = (u & 0x33333333) + ((u >> 2) & 0x33333333); u = (u & 0x0F0F0F0F) + ((u >> 4) & 0x0F0F0F0F); u = (u & 0x00FF00FF) + ((u >> 8) & 0x00FF00FF); u = (u & 0x0000FFFF) + ((u >> 16) & 0x0000FFFF); return u;}
略神呐><