定義:dp[i+1][j]前i種數字是否能構成j
為了用前i種數字加和成j,也就需要能用前i-1種數字加和成j,j-ai,···,j-mi x ai中的某一種。我們可以定義如下遞推關係:
dp[i+1][k] (0<=k<=mi且k x ai
<=j時存在dp[i][j-k x ai]為真的k)
int n,k,a[maxn]
,m[maxn]
;bool dp[maxn+1]
[maxk+1]
;void
solve()
}}}
這個演算法的複雜度是o(kσimi),這樣並不夠好。一般用dp求bool結果的話會有不少浪費,同樣的複雜度通常能獲得更多的資訊。在這個問題中,我們不光求出能否得到目標的和數,同時把得到時ai這個數還剩下多少個計算出來,這樣就能減少複雜度
dp[i+1][j]:=用前i種數加和得到j時第i種數最多能剩餘多少個(不能得到的話為-1)
按照如上所述定義遞推關係,這樣如果前i-1個數加和能得到j的話,第i個數就可以留下mi個。此外,前i種數加和出j-ai時第i種數還剩下k的話(k>0)用這i種數加和到j時第i種數就能剩下k-1個。由此我們可以得到下面的遞推式
dp[i+1][j]=mi(dp[i][j]>=0)||-1(ji或者dp[i+1][j-ai]<=0)||dp[i+1][j-ai]-1
這樣,只要看最終是否滿足dp[n][k]>=0就可以知道答案了
時間複雜度為o(nk)
int dp[maxk+1]
;void
solve()
else
if(j||dp[j-a[i]
]<=0)
else}}
if(dp[k]
>=0)
printf
("yes");
else cout <<
"no"
<< endl;
}
總結:如果前i個數字已經能夠組成數字j,那麼在當前狀態d[j]我就可以標記為m[i],即乙個都不用。如果沒有組成j並且j這個問題是被稱作最長上公升子串行(lis,longest increasing subsequence)的著名問題。這一問題通過使用dp也能很有效率地求解。
定義:dp[i]:以ai為末尾地最長上公升子串行的長度。
以ai結尾的上公升子串行是
1.只包含ai的子串行
2.在滿足jj
i的以aj為結尾的上公升子列末尾,追加上ai後得到的子串行
這二者之一,這樣就能得到如下遞推關係:
dp[i]=
} res=
max(res,dp[i]);
} cout << res << endl;
}此外還可以定義其他的遞推關係。前面我們利用dp求取正對最末尾的元素的最長子序列。如果子串行的長度相同,那麼最末尾的元素較小的在之後會更加有優勢,所以我們再反過來用dp針對相同長度情況下最小的末尾元素進行求解。
dp[i]:=長度為i+1的上公升子串行中末尾元素的最小值(不存在則為inf)
我們來看看如何用dp來更新這個陣列。
最開始全部dp[i]的值都初始化為inf。然後有前到後逐個考慮陣列的元素,對於每個aj,如果i=0或者或者dp[i-1]j的話就用dp[i]=min(dp[i],aj)進行更新。最終找出使得dp[i]2)的時間內給出結果,但這一演算法還可以
進行進一步優化。首先dp陣列中除inf之外都是單調遞增的,所以可以知道對於每個aj最多隻需要一次更新。對於這次更新究竟應該在什麼位置,不必逐個遍歷,可以利用二分搜尋,這樣就可以在o(nlogn)時間內求出結果
int dp[maxn]
;void
solve()
cout <<
lower_bound
(dp,dp+n,inf)
-dp << endl;
}
對於這個演算法以樣例為例,它真正的上公升子串行應該為,長度為3。但是如果我們用上面的演算法進行求解之後,長度雖然也為3,但是dp內記錄的上公升子串行卻時,顯然這並不是我們要求的最長上公升子串行。但是!要注意一點,我們這裡只求最長的上公升子串行的長度,我們本質要求的是前面的數比後面的數小就行了,並不要求將其序列展現出來。所以第乙個數為1還是2無關緊要,因為它們都比3要小。當前演算法遍歷一遍陣列a,如果不考慮每個實數的話,其實就是每次都將大的數新增到dp陣列的最後(當然小的數被更新到前面了,但是不影響我們的結果),最後求出的不是inf的個數就是我們最終的長度。
專欄lower_bound這個函式從已排好序的序列a中利用二分搜尋找出指向滿足ai>=k的ai的最小的指標。類似的函式還有upper_bound,這一函式求出的是滿足ai>k的ai的最小的指標。例:求出陣列a中k的個數upper_bound(a,a+n,k)-lower_bound(a,a+n,k);
挑戰程式設計競賽 page66這樣的劃分被稱作n的m劃分,特別的,m=n時稱作n的劃分數。dp不僅對於求解最優問題有效,對於各種排列組合的個數、概率或者期望之類的計算同樣很有用。在此,我們定義如下:
dp[i][j]=j的i劃分的總數(dp[m][n]就是代表n的m劃分)
將j分劃成i個的話,可以先取出k個,然後將剩下的j-k個分成i-1份,就可以得到下面的遞推式dp[i][j]=σj
k=0dp[i-1][j-k]。然而這個這個遞推式是不正確的,用這個辦法的話,例如1+1+2和1+2+1的劃分就當作不同的劃分來計數了。所以我們要考慮其他的遞推關係。
考慮n的m劃分ai(σm
i=1ai=n),如果對於每個i都有ai>0,那麼就對應了n-m的m劃分(如果每個ai>0那麼n的m劃分就是剛好有m組,每個都-1的話那麼就是n-m的m劃分)。另外,如果ai=0那麼就對應了n的m-1劃分。綜上我們就有了如下遞推關係。
dp[i][j]=dp[i][j-i]+dp[i-1][j] (剛好湊滿的加上沒有湊滿的)
這個遞推式可以不重複地計算所有地劃分,複雜度為o(nm)。
int n,m,m;
//輸入
int dp[maxm+1]
[maxn+1]
;//dp陣列
void
solve()
else}}
}
為了不重複技術,同一種類的物品最好一次性處理好。於是我們按照下面方式進行定義。
dp[i+1][j]:=從前i種物品中取出j個的組合總數
為了從前i種物品中取出j個,可以從前i-1種物品中取出j-k個,再從第i種物品中取出k個新增進來,所以有如下遞推關係:
dp[i+1][j]=σmin(j,a[i])
k=0dp[i][j-k]
直接計算這個遞推關係的話複雜度是o(nm2),不過因為我們有σmin(j,a[i])
k=0dp[i][j-k]=σmin(j-1,a[i])
k=0dp[i][j-1-k]+dp[i][j]-dp[i]j-1-ai
(前i種能湊出j-1種的加上前i-1種就能湊出j種的再減去前i-1種不能湊出a[i]種的)(dp[i+1][j-1]代表了我前i種能湊齊j-1個數,其中包括了也能湊齊dp[i+1][j]的數和不能湊齊dp[i+1][j]的數而不能湊齊dp[i+1][j]的數就是能湊齊dp[i][j-1-a[i]]的數)
int n,m;
int a[maxn]
;int dp[maxn+1]
[maxm+1]
;void
solve()
for(
int i=
0;i)else dp[i+1]
[j]=dp[i+1]
[j-1
]+dp[i]
[j];}}
cout << dp[n]
[m]<< endl;
}
mybatis學習筆記續
定義乙個介面類,乙個表對應的屬性的類,乙個資料連線配置檔案 public inte ce iuserdao after public void destoryall throws exception test public void find sqlsession.commit 上面的套路類推增,刪...
C 學習筆記 22
在c 中,auto ptr是乙個類,它用來實現對動態分配物件的自動釋放。建構函式與析構函式 auto ptr在構造時獲取對某個物件的所有權 ownership 在析構時釋放該物件。我們可以這樣使用auto ptr來提高 安全性,類似下面的 int p new int 0 auto ptr ap p ...
Python學習筆記 22
物件導向三大特徵及作用 多型我自己簡單總結一句話 使用方法 或者其他 時,不用考慮物件型別,適用性強,比如常見的len 多型 狗 哈士奇,泰迪,金毛。乙個物件可以以不同的形態去呈現 classa def init self,name self.name name property defname s...