这篇文章主要介绍线性dp
以李煜东大神的《算法竞赛进阶指南》和刘汝佳的紫书为蓝本
再加上几道比较复杂的dp题
线性dp暴力求解
POJ2279
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| // // main.cpp // POJ2279 // // Created by zhangmin chen on 2019/7/31. // Copyright © 2019 zhangmin chen. All rights reserved. //
using namespace std; typedef long long llong; typedef set<int>::iterator ssii;
const int maxn = 5 + 1;
int N[maxn], k;
void solve() { // _rep(i, 1, k) scanf("%d", &N[i]); while(k < 5) N[++k] = 0; // _rep(i, 0, 5) debug(N[i]); // cout << endl; llong f[N[1] + 1][N[2] + 1][N[3] + 1][N[4] + 1][N[5] + 1]; Set(f, 0); // then DP f[0][0][0][0][0] = 1; _rep(i, 0, N[1]) _rep(j, 0, N[2]) _rep(k, 0, N[3]) _rep(l, 0, N[4]) _rep(m, 0, N[5]) { if(i < N[1]) f[i + 1][j][k][l][m] += f[i][j][k][l][m]; if(j < N[2] && j < i) f[i][j + 1][k][l][m] += f[i][j][k][l][m]; if(k < N[3] && k < j) f[i][j][k + 1][l][m] += f[i][j][k][l][m]; if(l < N[4] && l < k) f[i][j][k][l + 1][m] += f[i][j][k][l][m]; if(m < N[5] && m < l) f[i][j][k][l][m + 1] += f[i][j][k][l][m]; } cout << f[N[1]][N[2]][N[3]][N[4]][N[5]] << endl; }
int main() { freopen("input.txt", "r", stdin); while(scanf("%d", &k) == 1 && k) solve(); }
|
刷表dp(递归求解)模版
OPENJUDGE2760
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| int dp[maxn][maxn]; int tb[maxn][maxn]; int n;
void init() { Set(dp, -1); Set(tb, 0); }
int solve(int i, int j) { if(dp[i][j] >= 0) return dp[i][j]; int& ans = dp[i][j]; return ans = tb[i][j] + (i == n ? 0 : max(solve(i + 1, j), solve(i + 1, j + 1))); }
int main() { freopen("input.txt", "r", stdin); init(); scanf("%d", &n); _rep(i, 1, n) _rep(j, 1, i) scanf("%d", &tb[i][j]); cout << solve(1, 1) << endl; }
|
DAG DP模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| class Rec { public: int a, b; };
Rec rec[maxn]; int G[maxn][maxn]; int n;
int F[maxn];
void init() { Set(G, 0); Set(F, -1); }
bool valid(Rec& lhs, Rec& rhs) { return (lhs.a < rhs.a && lhs.b < rhs.b) || (lhs.a < rhs.b && lhs.b < rhs.a); }
// i in j // G[i][j] = 1 // i -> j
int dp(int x) { if(F[x] != -1) return F[x]; int& ans = F[x]; ans = 1; _rep(i, 1, n) if(G[x][i]) ans = max(ans, dp(i) + 1); return ans; }
void printAns(int u) { printf("%d ", u); _rep(i, 1, n) if(G[u][i] && F[u] == F[i] + 1) { printAns(i); break; } return; }
int main() { freopen("input.txt", "r", stdin); init(); scanf("%d", &n); _rep(i, 1, n) scanf("%d%d", &rec[i].a, &rec[i].b); _rep(i, 1, n) _rep(j, 1, n) G[i][j] = valid(rec[i], rec[j]); _rep(i, 1, n) if(F[i] == -1) F[i] = dp(i); int tid = 0; _rep(i, 1, n) if(F[i] > F[tid]) tid = i; cout << F[tid] << endl; printAns(tid); cout << endl; }
|
DAG用解答树刷表法,思路很清晰
硬币问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| int V[maxn]; int S, N;
int minV[maxn], maxV[maxn]; int d[maxn]; int minCoin[maxn], maxCoin[maxn];
void init() { Set(V, 0); Set(minV, inf); Set(maxV, -inf); Set(d, -1); Set(minCoin, 0); Set(maxCoin, 0); }
void dp() { minV[0] = maxV[0] = 0; _rep(i, 1, S) _rep(j, 1, N) { if(i >= V[j]) { minV[i] = min(minV[i], minV[i - V[j]] + 1); maxV[i] = max(maxV[i], maxV[i - V[j]] + 1); } } printf("%d %d\n", minV[S], maxV[S]); }
// usage: // minCoin[coinValue] = coinID
void dp2() { minV[0] = maxV[0] = 0; _rep(i, 1, S) _rep(j, 1, N) if(i >= V[j]) { if(minV[i] > minV[i - V[j]] + 1) { minV[i] = minV[i - V[j]] + 1; minCoin[i] = j; } if(maxV[i] < maxV[i - V[j]] + 1) { maxV[i] = maxV[i - V[j]] + 1; maxCoin[i] = j; } } printf("%d %d\n", minV[S], maxV[S]); }
void printAns(int* d, int S) { while(S) { printf("%d ", d[S]); S -= V[d[S]]; } }
int main() { freopen("input.txt", "r", stdin); init(); scanf("%d%d", &N, &S); _rep(i, 1, N) scanf("%d", &V[i]); dp2(); printAns(minCoin, S); cout << endl; printAns(maxCoin, S); }
|
LCIS问题
LCIS最长公共上升子序列
algorithm
f(i,j) 表示 A[1⋯i]的子序列,B[1⋯j]的子序列构成的
并且以 B[j] 结尾的 LICS 长度
⎩⎪⎨⎪⎧f(i,j)=f(i−1,j)f(i,j)=0⩽k<j,Bk<Bjmaxf(i−1,k)+1Ai=BjAi=Bj
for ∀i∈[1,n]
for ∀j∈[1,m]
if Ai=Bj
for ∀k∈[0,j−1],if Bk<(Bj⇔Ai)
f(i,j)=max(f(i,j),f(i−1,k)+1)
优化
if Ai=Bj
for ∀k∈[0,j−1]if Bk<Ai
f(i,j)=max(f(i,j),f(i−1,k)+1)
等价于
{k∈[0,j−1]max[Ai−1][B0⋯Bj−1] ∣ Bk<(Bj⇔Ai)}+1f(i,j) {f(i−1,k) ∣ k∈[0,j−1],Bk<Ai}+1f(i,j) may use {f(i−1,k)∣k∈[0,j−1]}∪{f(i−1,j)} update maxv(when Bj<Ai)
利用前缀和思想,用一个maxv维护上面左边这一项
maxv=k∈[0,j−1]max{(Ai−1,Bk)} =max{(Ai−1,B0),(Ai−1,B1),⋯,(Ai−1,Bj−1)}
这样可以边对 j 循环边计算
for ∀j∈[1,m],if Bj<Ai
update maxv=max(maxv,f(i−1,j))
algorithm
for ∀i∈[1,n],maxv=0
if B0<Ai,maxv=f(i−1,0)
for ∀j∈[1,m]
if Ai=Bj, f(i,j)=maxv+1
else f(i,j)=f(i−1,j)
if Bj<Ai, maxv=max(maxv,f(i−1,j))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const int maxn = 3000 + 10; int f[maxn][maxn]; int A[maxn], B[maxn]; int n;
int main() { freopen("input.txt", "r", stdin); scanf("%d", &n); _rep(i, 1, n) scanf("%d", &A[i]); _rep(i, 1, n) scanf("%d", &B[i]);
_rep(i, 1, n) { int maxv = 0; if(B[0] < A[i]) maxv = max(maxv, f[i - 1][0]);
_rep(j, 1, n) { if(A[i] == B[j]) f[i][j] = maxv + 1; else f[i][j] = f[i - 1][j];
if(B[j] > A[i]) maxv = max(maxv, f[i - 1][j]); } } int res = 0; _rep(i, 1, n) res = max(res, f[n][i]); cout << res << endl; }
|
线性dp的序列问题
离散化和中位数优化
离散化和中位数优化是很重要的一种处理思想
序列问题的解决模版
POJ3666
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| const int maxn = 2000 + 10; const int inf = 0x3f3f3f3f;
int A[maxn], F[maxn][maxn]; int A2[maxn]; int n;
void init() { Set(A, 0); Set(A2, 0); ///Set(B, 0); Set(F, inf); }
int query(int x, int m) { return lower_bound(A2 + 1, A2 + 1 + m, x) - A2; }
int main() { freopen("input.txt", "r", stdin); init(); scanf("%d", &n);
_rep(i, 1, n) { scanf("%d", &A[i]); A2[i] = A[i]; }
sort(A2 + 1, A2 + 1 + n); int m = unique(A2 + 1, A2 + 1 + n) - A2 - 1; // id = query(A[i], m)
F[0][0] = 0;
_rep(i, 1, n) { int val = F[i - 1][0];
_rep(j, 1, m) { int baseline = A2[j]; val = min(val, F[i - 1][j]);
F[i][j] = val + abs(A[i] - baseline); } }
int ans = inf; _rep(j, 1, m) ans = min(ans, F[n][j]); cout << ans << endl;
}
|
带条件的状态转移方程
1 2
| 在dp的时候先判断valid() if(valid) continue;
|
对于非法状态,不执行转移
SPOJ-SERVICE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| const int maxn = 1000 + 10; const int maxp = 200 + 10; const int inf = 0x3f3f3f3f;
int L, N; int cost[maxp][maxp], P[maxn]; int F[maxn][maxp][maxp];
void init() { Set(P, 0); Set(cost, 0); Set(F, inf); }
void initDP() { F[0][1][2] = 0; P[0] =3; }
void dp(int& ans) { // debug(f[0]) _for(i, 0, N) _rep(x, 1, L) _rep(y, 1, L) { //debug(F[0][x][y]); int z = P[i];
if(x == z || y == z || x == y) continue;
F[i + 1][x][y] = min(F[i + 1][x][y], F[i][x][y] + cost[z][P[i + 1]]); F[i + 1][P[i]][y] = min(F[i + 1][P[i]][y], F[i][x][y] + cost[x][P[i + 1]]); F[i + 1][x][P[i]] = min(F[i + 1][x][P[i]], F[i][x][y] + cost[y][P[i + 1]]); }
_rep(x, 1, L) _rep(y, 1, L) { int z = P[N]; if(x == z || y == z || x == y) continue;
ans = min(ans, F[N][x][y]); } }
int main() { freopen("input.txt", "r", stdin); int T; scanf("%d", &T);
while (T--) { init();
scanf("%d%d", &L, &N);
_rep(i, 1, L) _rep(j, 1, L) cin >> cost[i][j]; _rep(i, 1, N) scanf("%d", &P[i]);
// then input finished assert(cost[0][0] == 0); //assert(P[6] == 5);
initDP(); int ans = inf; dp(ans); cout << ans << endl; } }
|