Dynamic Programming[0]
建北電資小社課
by
- 225班
- 電子計算機研習社_學術長
- 綽號807
- Everything starts from 0
這我

這是甚麼?
- 動態規劃
- 縮寫DP
- 從小問題答案,推得更大的答案
Dynamic Programming
一句話講完動態規劃
從小問題答案,推得更大的答案
圖形化解釋
要解決的
問題範圍
以解決的
小範圍問題1
以解決的
小範圍問題2
轉移式
轉移複雜度 轉移次數
複雜度
簡單公式
非簡單公式
前墜河
前綴和
這可能會用到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次
原陣列
加法次數
前綴和
- 第 個元素 需 次加法
- 元素 共需 次加法
- 複雜度:
暴力複雜度
暴力程式碼
#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
動態規劃: 轉移式
- 問題: 第 個的前綴和=?
- 解方:
- 轉移複雜度:
第 個前綴和=
第 個數 + 第 個的前綴和
動態規劃: 定狀態
- 問題: 第 個的前綴和=?
- 解方:
第 0 個元素
動態規劃: 複雜度
轉移複雜度 | 轉移次數 |
---|---|
n |
動態規劃: 複雜度
轉移複雜度 | 轉移次數 | 總複雜度 |
---|---|---|
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
- 快速求 區間和
前綴和: 更多
- 錯誤
求區間和 程式碼
#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
#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],輸出你能裝下的最大價值。
背包問題
0/1背包問題
dp[i]紀錄花費時間為i時拯救的最大價值
初始 dp[m+1]皆設為0
每次考慮第s個包包
轉移式 dp[i]=max(dp[i],dp[i-t[s]]+v[s])
i由大到小才不會重複計算
#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
每種東西都有無限個
無限背包問題
要改哪裡?
無限背包問題
i由小到大才會重複計算
每種東西有c[i]個
有限背包問題
一個一個算 這好像有點蠢
怎麼拆?
1、2、4、8、16、32、...&剩下的
有限背包問題
#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]
#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⁸⁰⁷
ckefgisc_dynamicProgramming0
- 360