这篇文章主要介绍线性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;     } }
   |