基礎的動態規劃
原理
動態規劃的思想的是把一個問題分解爲規模小一點的同樣的問題,然後找出小規模的問題和大規模問題的聯繫,從而遞歸地得出大規模問題的解。
實現方法
1.記憶化搜索(遞歸的思想)
從動態規劃的思想上理解,通過搜索的方式,將大問題分解爲小參數的性質相同的問題,但是一些不同參數的大問題可能會包含一些相同參數的小問題,對於這些小問題,我們搜索兩次太費時間,搜索完一次後,就將結果存下來,這就是所謂的記憶化搜索。
2.遞推關係式
就是直接列出大問題和小問題的聯繫,然後從小規模問題出發,組合小規模的問題得出大規模的問題。
3.例子
兩種形式是等價的,比如最簡單的求斐波那契數列。
//看這是從大規模的問題n開始入手的。記憶化搜索。
dp[n+1]
int f(int n){
if(有保存的結果) return dp[n];
return f(n-1)+f(n-2);
}
//這是從小規模(i=3)問題開始逐漸往上推的。
for(int i=3;i<n;i++)
dp[i]=dp[i-1]+dp[i-2];
步驟
- 邊界條件
- 遞推關係式
有關計數的動態規劃問題
重集的組合問題,比如經典的找錢問題,用多種面值的鈔票,湊一個數額的錢,每種鈔票都有充分多張。
POJ 2229
Description
Farmer John commanded his cows to search for different sets of numbers that sum to a given number. The cows use only numbers that are an integer power of 2. Here are the possible sets of numbers that sum to 7:
- 1+1+1+1+1+1+1
- 1+1+1+1+1+2
- 1+1+1+2+2
- 1+1+1+4
- 1+2+2+2
- 1+2+4
Help FJ count all possible representations for a given integer N (1 <= N <= 1,000,000).
Input
A single line with a single integer, N.
Output
The number of ways to represent N as the indicated sum. Due to the potential huge size of this number, print only last 9 digits (in base 10 representation).
解題思路
首先看題意,是想用這樣的數湊一個整數。有多少種湊法。
錯誤的思路
是不是可以定義sum[n]爲湊成整數n的方式的個數。
自然想到
解決了嗎,仔細想想看,不對這樣有重複,
比如說,前一個說明我們至少用了1一次,第二個說明我們至少用2一次,顯然他們不是互斥事件,相加有可能有重複。
這樣的纔是互斥,如至少用1一次和不用1。
正確的思路
顯然我們通過一個參數搞不定,這種情況一般就需要再添加一個參數,重新劃分問題,思考一哈,我們可以定義sum[i][n]指的是在前i箇中即,湊成n的方法數。
這樣我們就可以用上面提到的互斥的思路來對這個問題做一個劃分。
思考這個遞推關係時,
比如我們想說用對於第i個數即,這個起碼包含,我們絕對不用第i個數時的種類數即,還有就是我們至少用了一次第i個數,這相當於在前i個數中,任意挑選,湊成的方法數(其實就是用了那次把那次的值減去) 這樣遞歸關係式就準備好了。
邊界條件
顯然就是我們要湊的數爲0時,方法就一個。
然後就是當我們只用1時來湊任何數方法也就1個。
實現
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
#include <cmath>
//POJ Sumsets
int main(){
ll num;
cin>>num;
//因爲1000,000在2^20以內。
//我們可以通過求N在2^1-2^20。
//float x=log2(num); 是不是精準呢。lower_bound()
int x=2;
//特例
if(num<x) return 1;
//
int i;
for(i=1;i<=20;i++){
if(num<=x) break;
x=x*2;
}
i-=1;
//初始化邊界條件
vector<vector<int>> dp(i+1,vector<int>(num+1,0));
for(int j=1;j<=num;j++){
dp[0][j]=1;
}
for(int k=0;k<=i;k++){
dp[k][0]=1;
}
int mod=1e9;
//遞推式
//dp[k][j]=dp[k-1][j]+dp[k][j-2^k]
for(int k=1;k<=i;k++){
for(int j=1;j<=num;j++){
if((j-(1<<k))>=0)
dp[k][j]=(dp[k-1][j]+dp[k][j-(1<<k)])%mod;
else
dp[k][j]=dp[k-1][j]%mod;
}
}
cout<<dp[i][num]<<endl;
return 0;
}
小tip:可以從組合數學所講母函數的角度去思考。