Dynamic Programming[0]

建北電資小社課

by       

807
  • 225班
  • 電子計算機研習社_學術長
  • 綽號807
  • Everything starts from 0

這我

這是甚麼?

  • 動態規劃
  • 縮寫DP
  • 從小問題答案,推得更大的答案

Dynamic Programming

一句話講完動態規劃

從小問題答案,推得更大的答案

圖形化解釋

要解決的

問題範圍

以解決的

小範圍問題1

以解決的

小範圍問題2

轉移式

轉移複雜度    轉移次數

複雜度

\times

簡單公式

O(1)

非簡單公式

O(?)

前墜河

前綴和

這可能會用到dp的觀念

但通常前綴和就是前綴和

前綴

前綴和

5 3 7 2 4 8 1
  • 一個陣列
  • 所有前方元素和

原陣列

前綴和

  • 一個陣列
  • 所有前方元素和
5 3 7 2 4 8 1
5 3+5 3+5+7 3+5+7+2 3+5+7+2+4 3+5+7+2+4+8 3+5+7+2+4+8+1

原陣列

前綴和

前綴和

  • 所有前方元素和
  • 暴力加
5 3 7 2 4 8 1
5 8 15 17 21 29 30

2次

4次

7次

3次

1次

5次

6次

原陣列

加法次數

前綴和

  • 第  個元素 需          次加法

 

  •    元素 共需                     次加法

 

  • 複雜度: 

暴力複雜度

\frac{n*(n+1)}{2}
i
n
i+1
O(n^2)

暴力程式碼

#include<bits/stdc++.h>
using namespace std;
signed main(){
	int n;
	cin>>n;
	int a[n],pre[n]={0};
	for(int i=0;i<n;i++) cin>>a[i];
	// calculating prefix sum
	for(int i=0;i<n;i++){
		for(int j=0;j<=i;j++) pre[i]+=a[j];
	}
	// finish calculating
	for(int i=0;i<n;i++) cout<<pre[i]<<" ";
	cout<<"\n";
}
# PRESENTING CODE

動態規劃: 轉移式

  • 問題: 第   個的前綴和=?
  • 解方:

 

 

  • 轉移複雜度: 
i
i
i
i-1

第    個前綴和=

  第   個數 + 第         個的前綴和

O(1)

動態規劃: 定狀態

  • 問題: 第   個的前綴和=?
  • 解方:
0

第 0 個元素

動態規劃: 複雜度

轉移複雜度 轉移次數
n
O(1)

動態規劃: 複雜度

轉移複雜度 轉移次數 總複雜度
n
O(1)
O(n)

動態規劃程式碼

#include<bits/stdc++.h>
using namespace std;
signed main(){
	int n;
	cin>>n;
	int a[n],pre[n];
	for(int i=0;i<n;i++) cin>>a[i];
	// calculating prefix sum
	pre[0]=a[0];
	for(int i=1;i<n;i++) pre[i]=pre[i-1]+a[i];
	// finish calculating
	for(int i=0;i<n;i++) cout<<pre[i]<<" ";
	cout<<"\n";
}
# PRESENTING CODE

更好的 動態規劃程式碼

#include<bits/stdc++.h>
using namespace std;
signed main(){
	int n;
	cin>>n;
	vector<int> a(n);
	for(int i=0;i<n;i++) cin>>a[i];
	// calculating prefix sum
	for(int i=1;i<n;i++) a[i]+=a[i-1];
	// finish calculating
	for(int i=0;i<n;i++) cout<<a[i]<<" ";
	cout<<"\n";
}
# PRESENTING CODE
  • 快速求        區間和

前綴和: 更多

(l,r)
=pre[r]-pre[l-1]
  • 錯誤 
if(l==0)
=pre[r]

求區間和 程式碼

#include<bits/stdc++.h>
using namespace std;
signed main(){
	int n,q;
	cin>>n;
	vector<int> a(n);
	for(int i=0;i<n;i++) cin>>a[i];
	// calculating prefix sum
	for(int i=1;i<n;i++) a[i]+=a[i-1];
	// finish calculating
	cin>>q;
	for(int l,r;q--;){
		cin>>l>>r;
		cout<<a[r]-(l?a[l-1]:0)<<"\n";
	}
}
# PRESENTING CODE

你已經會了

通靈轉移式

寫出使狀態

講完了下課

常見dp問題

廢式數列

費氏數列

某貓要走n階樓梯,一次能向上跨一階或二階,有幾種走法?

1

0

5

2

3

4

1

1

2

3

5

8

走法數

階梯

dp[x]=dp[x-1]+dp[x-2]

轉移式

定狀態

定狀態

  • 初始值
  • 小問題的解

定狀態

  • 推得當前答案
  • 可利用已知

轉移式

#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
signed main(){
	int n;
	cin>>n;
	vector<int> dp(100,0);
	dp[0]=dp[1]=1;
	for(int s=2;s<100;s++) dp[s]=dp[s-1]+dp[s-2];
	for(int s,k=0;k<n;k++){
		cin>>s;
		cout<<dp[s]<<"\n";
	}
}
# PRESENTING CODE

網格路徑問題

有n條縱向及m條橫向的長方形路網中,求捷徑走法數。

網格路徑問題

dp[n][m]紀錄到i、j格上的走法數

初始 dp[0][0]=1;

轉移式 dp[i][j]=dp[i-1][j]+dp[i][j-1];

事實上可以C n+m取n

O(n \times m)
O(n+m)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
int pow(int x,int y){
    int ans=1;
    for(int base=x,s=1;s<=y;s<<=1,base=base*base%mod) if(s&y) ans=ans*base%mod;
    return ans; 
}
int divide(int x,int y){
    return x*pow(y,mod-2)%mod;
}
signed main(){
    int n,m;
    cin>>n>>m;
    if(n==1||m==1){
        cout<<1;
        return 0;
    }
    int d[n+m];
    d[0]=1;
    for(int s=1;s<n+m;s++) d[s]=d[s-1]*(s+1)%mod;
    cout<<divide(divide(d[n+m-3],d[n-2]),d[m-2]);
}
# PRESENTING CODE

網格路徑問題

網格路徑問題

背包問題

有一個背包,承重上限為x,現在有n個東西可以選擇是否放進背包,給你每個東西的重量w[i]和價值c[i],輸出你能裝下的最大價值。

背包問題

每種東西只有一個

ISCOJ4509

CSES1158

0/1背包問題

dp[i]紀錄花費時間為i時拯救的最大價值

初始 dp[m+1]皆設為0

每次考慮第s個包包

轉移式 dp[i]=max(dp[i],dp[i-t[s]]+v[s])

i由大到小才不會重複計算

O(m \times n)
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
    int n,m,sum;
    cin>>n>>m;
    vector<int> v(n),t(n);
    vector<int> dp(m+1,0);
    for(int &i:v) cin>>i,sum+=i;
    for(int &i:t) cin>>i;
    for(int s=0;s<n;s++){
        for(int d=m;d>=t[s];d--){
            dp[d]=max(dp[d],dp[d-t[s]]+v[s]);
        }
    }
    cout<<sum-dp[m];
    return 0;
}
# PRESENTING CODE

每種東西都有無限個

CSES1635

無限背包問題

要改哪裡?

無限背包問題

i由小到大才會重複計算

每種東西有c[i]個

有限背包問題

一個一個算  這好像有點蠢

怎麼拆?

1、2、4、8、16、32、...&剩下的

log(c[i])個
log(m \times n \times log(c[i]))

每種書都有無限個

TIOJ1387

ISCOJ4470

有限背包問題

#include<bits/stdc++.h>
using namespace std;
int main(){
        int n,t;
        cin>>n;
        vector<int> w(n),c(n),m(n);
        for(int i=0;i<n;i++) cin>>w[i]>>m[i]>>c[i];
        cin>>t;
        vector<int> dp(t+1,0);
        for(int i=0,mm,mw;i<n;i++){
                for(int a=1;a<c[i];c[i]-=a,a<<=1){
                        mm=m[i]*a,mw=w[i]*a;
                        for(int j=t;j>=mw;j--) dp[j]=max(dp[j],dp[j-mw]+mm);
                }
                mm=m[i]*c[i],mw=w[i]*c[i];
                for(int j=t;j>=mw;j--) dp[j]=max(dp[j],dp[j-mw]+mm);
        }
        cout<<dp[t]<<"\n";
}
# PRESENTING CODE

LIS

Longest Increasing Subsequence

給你n個數,求最長遞增子序列

最常遞增子序列

lis[]就是LIS

每次比較x[i]

比所有LIS中的元素小: lis[0]=x[i]

比所有LIS中的元素大: lis[lislen++]=x[i]

否則: 將LIS中最小大於x[i]者=x[i]

O(n log(n))
#include<bits/stdc++.h>
using namespace std;
 
int main(){
    ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n;
    cin>>n;
    vector<int> x(n,0);
    vector<int> lis(n,1e9+7);
    int Max=1;
    for(int &i:x) cin>>i;
    lis[0]=x[0];
    for(int &i:x){
        if(i==lis[Max-1]||i==lis[0]) continue;
        else if(i>lis[Max-1]) lis[Max++]=i;
        else if(i<=lis[0]) lis[0]=i;
        else for(int l=0,r=Max,mid=l+r>>1;r;mid=l+r>>1){
            if(r-l==1) lis[r]=i,r=0;
            else if(lis[mid]<i) l=mid;
            else if(lis[mid]>i) r=mid;
            else break;
        }
    }
    cout<<Max<<endl;
}
# PRESENTING CODE

LCS

直接看題目

answer

#include<bits/stdc++.h>
using namespace std;
int main(){
    string a,b;
    int lena,lenb;
    cin>>lena>>lenb>>a>>b;
    int dp[lena][lenb]={0};
    if(a[0]==b[0]) dp[0][0]=1;
    for(int s=1;s<lena;s++) dp[s][0]=max(dp[s-1][0],(a[s]==b[0])?1:0);
    for(int d=1;d<lenb;d++) dp[0][d]=max(dp[0][d-1],(a[0]==b[d])?1:0);
    for(int s=1;s<lena;s++){
        for(int d=1;d<lenb;d++){
            dp[s][d]=max(dp[s-1][d],dp[s][d-1]);
            if(a[s]==b[d]) dp[s][d]=max(dp[s-1][d-1]+1,dp[s][d]);
        }
    }
    cout<<dp[lena-1][lenb-1];
}
# PRESENTING CODE

ckefgisc_dynamicProgramming0

By 建中店自計算機研習社學術長807⁸⁰⁷