DP
概念
狀態、轉移方程、初始化
先放一張圖(相信都能理解:狀態、轉移方程、初始化的含義,隨便引入斐波那契數列的題)
入門題
Problem 1
斐波那契數列
組合數
轉移方程:
楊輝三角:
Problem 2
\(N\cdot M\)的方格圖只能向右或者向下求走到右下的方案數?
和走到右下的最小代價?
Problem 3
數字三角形給你一個三角形,問從怎麼走能夠取得最大代價
Problem 4
在上一個題的基礎上變化為求和 \(\bmod100\)之後最大的結果
多一個條件
多一維狀態
Problem 5
最長上升子序列求方案
dp 狀態的判定
思考什麼東西發生變化
\(f[\)位置\(][\)和\(]\implies f[i][j][k]\) 是否可能
走到 \((i, k)\) 和為 \(k\)
int f[位置] = 和
\(f[i][j]\) 走到 \((i, k)\) 和最大是多少
Problem 6
滑雪 \(N\) 行 \(M\) 列的圖
- 每個格子有高度
- 可以滑向周圍四個比自己矮的格子
- 最多能劃多遠
思路:
排序之後就是前面一道題
#include <iostream>
#include <queue>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cstdio>
#include <set>
#include <map>
#include <unordered_map>
#include <bitset>
using namespace std;
const int MAXX = 1e5 + 10, MAXN = 1e9 + 10;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n, m;
int h[233][233], f[233][233];//f[i][j]代表從(i, j)這個位置出發 最遠能滑多遠
bool g[233][233];//g[i][j]代表f[i][j]算過沒
int dx[10] = {-1, 1, 0, 0};//上下左右
int dy[10] = {0, 0, -1, 1};//dx[i], dy[i]代表的是在第i個方向下x的座標和y座標的偏差值
const int M = 250 * 250;
pair<int, int> z[M];//z[i]代表一個座標
bool cmp(pair<int, int> a, pair<int, int> b){
return h[a.first][a.second] > h[b.first][b.second];
}
int main(){
n = read(), m = read();
int k = 0;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
h[i][j] = read();//每個位置的高度
k++;
z[k] = make_pair(i, j);
}
}
sort(z + 1, z + k + 1, cmp);
for(int a = 1;a <= k;a++){
int i = z[a].first;
int j = z[a].second;
//取出當前座標
//int h = ::h[i][j];
//取出當前座標高度
f[i][j] = max(f[i][j], 1);
for(int d = 0;d < 4;d++){
int x = i + dx[d];
int y = j + dy[d];
//從 (i,j)沿著方向d走一格會走到(x, y)
if(x >= 1 && x <= n && y >= 1 && y <= m)//判斷(x, y)是否合法
if(h[i][j] > h[x][y])//(i, j)高度比(x, y)高度更高
f[x][y] = max(f[x][y], f[i][j] + 1);
}
}
int ans = -1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
ans = max(ans, f[i][j]);
cout << ans << endl;
return 0;
}
Problem 7
烏龜棋
- 長度為 \(N\) 的格子上有權值
- \(M\) 張牌,上面有 \(1-4\) 中的一個數
- 使用一張牌往前走幾步
- 最大化經過的位置的權值和
思路:
f[i][j][k][l][r]
考慮最佳化
#include<bits/stdc++.h>
using namespace std;
int f[45][45][45][45];
int g[5];
int a[400];
int main()
{
int n,m;
cin>>n>>m;
int i,j,k,l;
for(i=1;i<=n;i++)
cin>>a[i];
f[0][0][0][0]=a[1];
for(i=1;i<=m;i++){
int x;
cin>>x;
g[x]++;
}
for(i=0;i<=g[1];i++)
for(j=0;j<=g[2];j++)
for(k=0;k<=g[3];k++)
for(l=0;l<=g[4];l++){
int move=1+i+2*j+3*k+4*l;
if(i!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l]+a[move]);
if(j!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l]+a[move]);
if(k!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k-1][l]+a[move]);
if(l!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k][l-1]+a[move]);
}
cout<<f[g[1]][g[2]][g[3]][g[4]]<<endl;
return 0;
}
區間dp
所有的操作都是針對區間進行,不會有跨越兩個元素的情況
Problem 1
合併石子
- 每次選擇相鄰兩堆
- 代價為兩堆石子和
- 問最小總代價
思路:
用 \(𝑓[𝑚][𝑟]\) 表示一段區間合併的最小代價
列舉斷點合併
如果是環形要如何處理?
Problem 2
給出一個的只有 ()[]
四種括號組成的字串,求最多能夠選出多少個括號滿足完全匹配
思路:
\(𝑓[𝑚][𝑟]\)代表該段區間能匹配的最多字元
- 轉移一 列舉斷點
- 轉移二 去掉端點
- 轉移三 匹配端點
Problem 3
給你 \(n\) 個數字
- 要求不能刪除左右端點的數字
- 刪除其他數字的代價是該數字和左右相鄰數字的乘積
- 問把數字(除端點)刪完後的最小總代價
還是 \(𝑓[𝑚][𝑟]\)
注意:狀態要表示為保留左右兩張卡片
Problem 4
給定字串,求迴文子序列的數量
注:兩個迴文子序列只要位置不同就算做不同的迴文子序列
思路:
關鍵:去重
- 轉移一:容斥原理
- 轉移二:當左右字元相同時 再補充一次內部答案
Problem 5
給定凸 \(N\) 邊形
- 每個點有一個權值
- 將凸多邊形三角化
- 每個三角形的三個點權乘起來求和
- 問最小和
思路:
本質上是一個環形區間dp
揹包
Problem 1
\(N\) 個物品 \(M\) 容量每個物品有體積和價值
- 最大化價值和
- 如果物品可以用無限次?
- 如果物品可以用有限次?
樹形dp
本質:
基於樹的dp
-
dp方法始終為從下至上進行dp
-
在每個節點對所有兒子做聚合
-
可能需要多一遍 dfs 或者 bfs
圖的儲存
#include <iostream>
#include <queue>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cstdio>
#include <set>
#include <map>
#include <unordered_map>
#include <bitset>
using namespace std;
const int MAXX = 1e5 + 10, MAXN = 1e9 + 10;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n;
vector<int> z[MAXX];//z[i]代表從i出發的所有邊 z[i][j]代表從i出發的第j條邊會走到z[i]
int f[MAXX];
void dfs(int i, int fa){//當前dfs到了i點他的父親是fa
//要求f[i]
for(auto j : z[i]){//從i->j的邊
if(j != fa){
dfs(j, i);
}
}
f[i] = 1;
for(auto j : z[i]){//從i->j的邊
if(j != fa){
f[i] += f[j];
}
}
}
int main(){
n = read();
for(int i = 1;i <= n;i++){
int s, e;
s = read(), e = read();
z[s].push_back(e);
z[e].push_back(s);
}
dfs(1, 0);
cout << f[1] << '\n';
return 0;
}
Problem 1
求樹上有多少個點
思路:
Problem 2
求樹的直徑
思路:
\(F[i][0]\) 最長
\(F[i][1]\) 次長
#include <iostream>
#include <queue>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cstdio>
#include <set>
#include <map>
#include <unordered_map>
#include <bitset>
using namespace std;
const int MAXX = 1e5 + 10, MAXN = 1e9 + 10;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n;
int ans;
vector<int> z[MAXX];//z[i]代表從i出發的所有邊 z[i][j]代表從i出發的第j條邊會走到z[i]
int f[MAXX][2];//f[i][0/1]從i出發向下走最長或次長的路徑長度
void dfs(int i, int fa){//當前dfs到了i點他的父親是fa
//要求f[i]
for(auto j : z[i]){//從i->j的邊
if(j != fa){
dfs(j, i);
}
}
priority_queue<int> heap;
heap.push(0);
heap.push(0);
for(auto j : z[i]){//從i->j的邊
if(j != fa){
heap.push(f[j][0] + 1);
}
}
f[i][0] = heap.top();
heap.pop();
f[i][1] = heap.top();
ans = max(ans, f[i][0] + f[i][1]);
}
int main(){
n = read();
for(int i = 1;i <= n;i++){
int s, e;
s = read(), e = read();
z[s].push_back(e);
z[e].push_back(s);
}
dfs(1, 0);
cout << f[1] << '\n';
return 0;
}
Problem 3
求樹上路徑總長度和
思路:
F[i]
表示子樹內答案
DP + Dfs
Problem 5
現在要在一棵樹上佈置士兵,每個士兵在結點上,每個士兵可以守護其結點直接相連的全部點,問最少需要佈置多少個士兵。
Problem 6
一棵結點帶權樹,大小(結點數)為 \(k\) 的子樹的權值和最大為多少。
資料範圍 \(100\) 以內
Problem 7
依賴揹包問題
每個物品要選必須先選某個指定的物品,問能夠獲得的最大價值