OI模板 DP模型 线性/区间/树形
线性DP
最长上升子序列( O ( n 2 ) O(n^2) O(n2))
F [ i ] = max 1 ≤ j < i , a j < a i { F [ j ] + 1 } F[i] = \max\limits_{1\leq j<i,a_j<a_i}~\{F[j]+1\} F[i]=1≤j<i,aj<aimax {F[j]+1}
int ans = 1;
for(int i = 1; i <= n; ++ i){
dp[i] = 1;
for(int j = 1; j < i; ++ j)
if(a[j] < a[i])
dp[i] = max(dp[i], dp[j] + 1);
ans = max(ans, dp[i]);
}
最长上升子序列( O ( n log n ) O(n\log n) O(nlogn))
vector<int> s;
s.push_back(a[1]);
for(int i = 2; i <= n; ++ i){
if(a[i] > s.back()) s.push_back(a[i]);
else *lower_bound(s.begin(), s.end(), a[i]) = a[i];
}
ans = s.size();
最长公共子序列
F [ i , j ] = max { F [ i − 1 , j ] F [ i , j − 1 ] F [ i − 1 , j − 1 ] + 1 i f a i = = b j F[i,j] = \max\begin{cases}F[i-1,j] \\ F[i,j-1] \\ F[i-1,j-1]+1~~~~if ~a_i==b_j\end{cases} F[i,j]=max⎩⎪⎨⎪⎧F[i−1,j]F[i,j−1]F[i−1,j−1]+1 if ai==bj
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= m; ++ j){
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
if(a[i] == b[j]) dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
}
ans = dp[n][m];
背包
01背包求最大收益
F [ i , j ] = max { F [ i − 1 , j ] F [ i − 1 , j − V i ] + W i i f j ≥ V i F[i,j] = \max\begin{cases} F[i-1,j] \\ F[i-1,j-V_i]+W_i~~if~j\geq V_i \end{cases} F[i,j]=max{F[i−1,j]F[i−1,j−Vi]+Wi if j≥Vi
const int N=1;
int n,m,dp[N],v[N],w[N];
//物品数、背包容量、dp数组、物品体积、物品价值
int DP(){
memset(dp,0xcf,sizeof(dp));
for(int i=1; i<=n; ++i)
for(int j=m; j>=v[i]; --j)
dp[j]=std::max(dp[j],dp[j-v[i]]+w[i]);
return dp[m];
}
例题:Luogu P1048 [NOIP2005 普及组] 采药。
01背包求方案个数
const int N=1;
int n,m,dp[N],v[N];
//物品数、背包容量、dp数组、物品体积
int DP(){
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int i=1; i<=n; ++i)
for(int j=m; j>=v[i]; --j)
dp[j]+=dp[j-v[i]];
return dp[m];
}
例题:Luogu P1164 小A点菜。
完全背包求最大收益
const int N=1;
int n,m,dp[N],v[N],w[N];
//物品数、背包容量、dp数组、物品体积、物品价值
int DP(){
for(int i=1; i<=n; ++i)
for(int j=v[i]; j<=m; ++j)
dp[j]=std::max(dp[j],dp[j-v[i]]+w[i]);
return dp[m];
}
完全背包求方案个数
与 01背包类似。
例题:Luogu P1832 A+B Problem(再升级)。
多重背包直接拆分法
拆分+01背包。
多重背包二进制拆分法
const int N=1;
int n,m,dp[N],v[N],w[N],cnt;
//物品数、背包容量、dp数组、物品体积、物品价值、拆分后物品数
int DP(){
int v1,w1,c1;
for(int i=1; i<=n; ++i){//拆分
scanf("%d%d%d",&v1,&w1,&c1);
for(int j=1; j<=c1; j<<=1)
v[++cnt]=j*v1,w[cnt]=j*w1,c1-=j;
if(c1) v[++cnt]=c1*v1,w[cnt]=c1*w1;
}
for(int i=1; i<=cnt; ++i)//01背包
for(int j=m; j>=w[i]; --j)
dp[j]=std::max(dp[j],dp[j-w[i]]+v[i]);
return dp[m];
}
例题:Luogu P1776 宝物筛选。
多重背包单调队列优化法
gugugu
分组背包
F [ i , j ] F[i,j] F[i,j]:前 i i i 组选出价值为 j j j 的物品最大收益。
F [ i , j ] = max { F [ i − 1 , j ] max 1 ≤ k ≤ C i F [ i − 1 , j − V i , k ] + W i , k F[i,j]=\max\begin{cases} F[i-1,j] \\ \max\limits_{1\leq k\leq C_i}F[i-1,j-V_{i,k}]+W_{i,k} \end{cases} F[i,j]=max{F[i−1,j]1≤k≤CimaxF[i−1,j−Vi,k]+Wi,k
const int N=1010;
int n,m,dp[N],v[N],w[N];
//物品数、背包容量、dp数组、物品体积、物品价值
int g[N][N],c[N],cnt;
//第i组第j个物品编号、第i组物品数、组数
int DP(){
for(int i=1; i<=cnt; ++i)
for(int j=m; j>=0; --j)
for(int k=1; k<=c[i]; ++k)
if(j>=v[g[i][k]])
dp[j]=std::max(dp[j],dp[j-v[g[i][k]]]+w[g[i][k]]);
return dp[m];
}
区间DP
用 F ( l , r , s ) F(l,r,s) F(l,r,s) 表示区间 [ l , r ] [l,r] [l,r] 在 s s s 限制下的最优解。
常见转移:
F ( l , r , s ) = max l ≤ k < r { F ( l , k , s ) , F ( k + 1 , r , s ) } F(l,r,s) = \max\limits_{l \leq k < r}\{F(l,k,s), F(k+1,r,s)\} F(l,r,s)=l≤k<rmax{F(l,k,s),F(k+1,r,s)}
F ( l , r , s ) = max { F ( l + 1 , r , s ) , F ( l , r − 1 , s ) } F(l,r,s) = \max\{F(l+1,r,s), F(l,r-1,s)\} F(l,r,s)=max{F(l+1,r,s),F(l,r−1,s)}
树形DP
最大权独立集问题
F [ x , 0 ] = ∑ s ∈ S o n ( x ) max ( F [ s , 0 ] , F [ s , 1 ] ) F[x,0]=\displaystyle\sum\limits_{s\in Son(x)} \max(F[s,0],F[s,1]) F[x,0]=s∈Son(x)∑max(F[s,0],F[s,1])
F [ x , 1 ] = W e i x + ∑ s ∈ S o n ( x ) F [ s , 0 ] F[x,1]=Wei_x+\displaystyle\sum\limits_{s\in Son(x)} F[s,0] F[x,1]=Weix+s∈Son(x)∑F[s,0]
a n s = max ( F [ x , 0 ] , F [ x , 1 ] ) ans = \max(F[x,0],F[x,1]) ans=max(F[x,0],F[x,1])
const int N = 10010;
int n, inD[N], Wei[N], dp[N][2], rt;//节点数、入度、权值、DP数组、根
vector<int> Edge[N];//图
void dfs(int x){
dp[x][0] = 0, dp[x][1] = Wei[x];
for(int i = 0; i < Edge[x].size(); ++ i){
int y = Edge[x][i];
dfs(y);
dp[x][0] += max(dp[y][0], dp[y][1]);
dp[x][1] += dp[y][0];
}
return ;
}
//main()函数:
dfs(rt);
ans = max(dp[rt][0], dp[rt][1]);
最小权覆盖集问题
F [ x , 0 ] = ∑ s ∈ S o n ( x ) F [ s , 1 ] F[x,0]=\displaystyle\sum\limits_{s\in Son(x)} F[s,1] F[x,0]=s∈Son(x)∑F[s,1]
F [ x , 1 ] = ∑ s ∈ S o n ( x ) min ( F [ s , 0 ] , F [ s , 1 ] ) F[x,1]=\displaystyle\sum\limits_{s\in Son(x)} \min(F[s,0],F[s,1]) F[x,1]=s∈Son(x)∑min(F[s,0],F[s,1])
a n s = min ( F [ x , 0 ] , F [ x , 1 ] ) ans = \min(F[x,0],F[x,1]) ans=min(F[x,0],F[x,1])
const int N = 10010;
vector<int> Edge[N];//图
int n, dp[N][2];//节点数、DP数组
void dfs(int x, int fa){
dp[x][1] = 1, dp[x][0] = 0;
for(int i = 0; i < Edge[x].size(); ++ i){
int y = Edge[x][i];
if(y == fa) continue;
dfs(y, x);
dp[x][0] += dp[y][1];
dp[x][1] += min(dp[y][0], dp[y][1]);
}
return ;
}
//main()函数:
dfs(1, 0);
ans = min(dp[1][0], dp[1][1]);
例题:Luogu P2016 战略游戏。
树形依赖背包
F [ x , t ] F[x,t] F[x,t]:在以 x x x 为根的子树中选 t t t 个物品的最大收益。
技巧:如有多个节点为根,新建超级根 0 0 0。
F [ x , t ] = max ∑ i = 1 p c i = t − 1 { ∑ i = 1 p F [ y i , c i ] } + W e i x F[x,t]=\max\limits_{\sum_{i=1}^p~c_i=t-1} \bigg\{\displaystyle\sum\limits_{i=1}^pF[y_i,c_i]\bigg\}+Wei_x F[x,t]=∑i=1p ci=t−1max{i=1∑pF[yi,ci]}+Weix
const int N = 310;
int n, m, Wei[N], dp[N][N];//总物品数、选择物品数、物品重量、DP数组
vector<int> Edge[N];//图
void dfs(int x){
dp[x][0] = 0;
for(int i = 0; i < Edge[x].size(); ++ i){
int y = Edge[x][i];
dfs(y);
for(int t = m; t >= 0; -- t)//当前背包体积
for(int j = 0; j <= t; ++ j)//组内物品
dp[x][t] = max(dp[x][t], dp[x][t-j] + dp[y][j]);
}
if(x)//超级根 0 不用占物品数
for(int t = m; t > 0; -- t)
dp[x][t] = dp[x][t-1] + Wei[x];
return ;
}