Loading

【做题笔记】Atcoder 之 dp 专题训练

A

B

C

D

E

F

G

H

I

概率 dp。

\(dp_{i,j}\) 表示前 \(i\) 个硬币中有 \(j\) 个正面的概率。转移显然:

\(dp_{i,j}=dp_{i-1,j-1}\times p_i+dp_{i-1,j}\times (1-p_i)\)

\(j=0\) 时,前 \(i\) 个硬币中没有正面。所以只能由反面的概率转移过来,转移为:

\(dp_{i,j}=dp_{i-1,j}\times (1-p_i)\)

初始化 \(dp_{0,0}=1\)

时间复杂度 \(O(n^2)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)

using namespace std;

const int N = 3005;

double ans, p[N], dp[N][N];

int n;

signed main() {
  cin >> n;
  For(i,1,n) cin >> p[i];
  dp[0][0] = 1;
  For(i,1,n) {
    For(j,0,i) {
      if(j == 0) dp[i][j] = dp[i-1][j] * (1 - p[i]);
      else dp[i][j] = dp[i-1][j-1] * p[i] + dp[i-1][j] * (1 - p[i]);
    }
  }
  For(i,n/2+1,n) {
    ans += dp[n][i];
  }
  printf("%.10lf\n", ans);
  return 0;
}

J

K

博弈论 dp。

\(dp_i\) 表示剩下 \(i\) 个石子的胜败态。考虑到能走到必败态的就一定是必胜态,进行转移即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)

using namespace std;

const int N = 105, M = 2e5 + 10;

int n, k, a[N];

bool dp[M];

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n >> k;
  For(i,1,n) cin >> a[i];
  For(j,0,k) {
    For(i,1,n) {
      if(a[i] <= j) dp[j] |= (dp[j-a[i]] == 0);
    }
  }
  cout << (dp[k] ? "First\n" : "Second\n");
  return 0;
}

L

M

数数 dp。

\(dp_{i,j}\) 表示前 \(i\) 个人分 \(j\) 块糖果的方案数。转移为:

\(dp_{i,j} = \sum\limits_{x=0}^{min(j,a_i)}dp_{i-1,j-x} = \sum\limits_{x=j-min(j,a_i)}^{j}dp_{i-1,x}\)

答案为 \(dp_{n,k}\)

上者使用前缀和优化即可。

时间复杂度 \(O(nk)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;ri>=l;--i)
#define mod 1000000007

using namespace std;

const int N = 105, M = 1e5 + 10;

int n, K, a[N], dp[N][M], sum[N][M];

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n >> K;
  For(i,1,n) cin >> a[i];
  dp[0][0] = sum[0][0] = 1;
  For(i,1,K) sum[0][i] += sum[0][i-1]; 
  For(i,1,n) {
    For(j,0,K) {
      if(j == min(j,a[i])) dp[i][j] = sum[i-1][j] ;
      else dp[i][j] = (sum[i-1][j] - sum[i-1][j-min(j,a[i])-1] + mod) % mod;
      sum[i][j] = (sum[i][j-1] + dp[i][j]) % mod;
    }
  }
  cout << dp[n][K] << '\n';
  return 0;
}

N

O

状压 dp。

\(dp_{i,S}\) 表示左部点前 \(i\) 个点完全匹配,右部点状态为 \(S\) 的方案数。转移为:

\(dp_{i,S}=\sum\limits_{to(i,j)}dp_{i-1,S-j}\),其中 \(S-j\) 表示 \(S\) 状态中去掉 \(j\) 的状态。同时要保证 \(S\) 状态中 \(j\) 的状态为 \(1\)

答案为 \(dp_{n,2^n-1}\)

时间复杂度 \(O(n^2 2^n)\),卡常可过。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 1000000007

using namespace std;

const int N = 22, M = (1<<22)+1;

int n, a[N][N], dp[N][M], to[N][N], len[N];

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  For(i,1,n) {
    For(j,1,n) {
      cin >> a[i][j];
      if(a[i][j]) {
        to[i][++len[i]] = j;
      }
    }
  }
  dp[0][0] = 1;
  For(i,1,n) {
    For(j,1,len[i]) {
      int x = to[i][j]-1;
      for (int S = 0; S < (1<<n); S++) {
        if((S >> x) & 1) {
          dp[i][S] = (dp[i][S] + dp[i-1][S ^ (1 << x)]) % mod;
        }
      }
    }
  }
  cout << dp[n][(1<<n)-1] << '\n';
  return 0;
}

P

数数 dp,类似 没有上司的舞会

\(dp_{x,0/1}\) 表示 \(x\) 节点为白或黑点的方案数。转移为:

\(dp_{x,0}=\prod_{y\in son(x)} (dp_{y,0}+dp_{y,1})\)
\(dp_{x,1}=\prod_{y\in son(x)} dp_{y,0}\)

答案为 \(dp_{1,0}+dp_{1,1}\)

时间复杂度 \(O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 1000000007

using namespace std;

const int N = 1e5 + 10;

struct Node {
  int v, nx;
} e[N << 1];

int n, h[N], tot, dp[N][2];

void add(int u, int v) {
  e[++tot] = (Node){v, h[u]};
  h[u] = tot;
}

void dfs(int x, int fa) {
  dp[x][0] = dp[x][1] = 1;
  for (int i = h[x]; i; i = e[i].nx) {
    int y = e[i].v;
    if(y == fa) continue;
    dfs(y, x);
    dp[x][0] = (dp[x][0] * (dp[y][0] + dp[y][1]) % mod) % mod;
    dp[x][1] = (dp[x][1] * dp[y][0]) % mod;
  }
}

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  For(i,1,n-1) {
    int u, v;
    cin >> u >> v;
    add(u, v); add(v, u);
  } 
  dfs(1, 0);
  cout << (dp[1][0] + dp[1][1]) % mod;
  return 0;
}

Q

带权最长上升子序列

\(dp_i\) 表示以 \(i\) 结尾的最长上升子序列的最大权值。显然有:

\(dp_i=\max\limits_{j=1}^{i-1}dp_j+a_i\)

维护 \(dp\) 的前缀最大值并且支持插入数即可。树状数组即可胜任。

时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 1000000007

using namespace std;

const int N = 2e5 + 10;

int n, h[N], a[N], t[N], dp[N], ans;

int lb(int x) {
  return x & -x;
}

void add(int x, int val) {
  for (int i = x; i <= N-2; i += lb(i)) {
    t[i] = max(t[i], val);
  }
}

int Max(int x) {
  int res = 0;
  for (int i = x; i; i -= lb(i)) {
    res = max(res, t[i]);
  }
  return res;
}

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  For(i,1,n) cin >> h[i];
  For(i,1,n) cin >> a[i];
  For(i,1,n) {
    dp[i] = Max(h[i]-1) + a[i];
    add(h[i], dp[i]);
    ans = max(ans, dp[i]);
  }
  cout << ans << '\n';
  return 0;
}

R

矩阵优化 dp。

\(dp_{k,i}\) 表示经过长度为 \(k\),当前从某点转移至 \(i\) 的方案数。转移有:

\(dp_{k,i}=\sum\limits_{to(j,i)}dp_{k-1,j}\)

答案为 \(\sum\limits_{i=1}^n dp_{k,i}\)

可以发现每一次 sigma 内的转移都是固定的,这样重复有规律的转移可以用矩阵快速幂进行优化。

设计矩阵

\[\begin{bmatrix} a_{1,1}&a_{2,1}&\cdots&a_{n,1}\\ a_{1,2}&a_{2,2}&\cdots&a_{n,2}\\ \vdots&\vdots&\ddots&\vdots\\ a_{1,n}&a_{2,n}&\cdots&a_{n,n} \end{bmatrix} \]

对此矩阵进行 \(k\) 次快速幂,再与全 \(1\) 矩阵相乘。结果矩阵第一列即为答案。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 1000000007

using namespace std;

const int N = 55;

int n, k, a[N][N], ans;

struct Matrix {
  int M[N][N];
  void clear() {
    For(i,1,n) For(j,1,n) M[i][j] = 0;
  }
  void init() {
    clear();
    For(i,1,n) M[i][i] = 1;
  }
  Matrix friend operator * (const Matrix &A, const Matrix &B) {
    Matrix Ans;
    Ans.clear();
    For(i,1,n) {
      For(j,1,n) {
        For(k,1,n) {
          Ans.M[i][j] = (Ans.M[i][j] + (A.M[i][k] * B.M[k][j]) % mod) % mod;
        }
      } 
    }
    return Ans;
  }
} dp;

Matrix qpow(Matrix a, int b) {
  Matrix res; res.init();
  for (; b; b >>= 1, a = a * a) {
    if(b & 1) res = res * a;
  }
  return res;
}

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n >> k;
  For(i,1,n) For(j,1,n) cin >> a[i][j];
  For(i,1,n) For(j,1,n) dp.M[i][j] = a[j][i];
  Matrix Res; Res.clear();
  For(i,1,n) Res.M[i][1] = 1;
  Res = qpow(dp, k) * Res;
  For(i,1,n) ans = (ans + Res.M[i][1]) % mod;
  cout << ans << '\n';
  return 0;
}

S

数位 dp。

\(dp_{pos,sum,0/1}\) 表示处理到第 \(pos\) 位,总和对 \(D\) 取模的结果为 \(sum\),是否达到上限。

考虑记忆化搜索,每位数值从 \(0\) 枚举至 \(maxx\)\(maxx\) 取值关乎于是否达到上限。

上限的判定为从高位至低位的前缀一致(\(K\) 的前缀)。当达到上限时,\(maxx\)\(K\) 的第 \(pos\) 位即可。

答案满足要求显然就是 \(sum=0\) 时。

以上为搜索统计答案,只要记忆化一下即可。

时间复杂度 \(O(可过)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 1000000007

using namespace std;

const int N = 1e5 + 10, D = 105;

string k;

int n, d, num[N], dp[N][D][2];

int dfs(int pos, int res, int lim) {
  if(pos == 0) return (res == 0);
  if(dp[pos][res][lim] != -1) return dp[pos][res][lim];
  int ans = 0, maxx = (lim ? num[pos] : 9);
  For(i,0,maxx) {
    ans = (ans + dfs(pos - 1, (res + i) % d, lim && (i == maxx))) % mod;
  }
  return dp[pos][res][lim] = ans;
}

int ans() {
  memset(dp, -1, sizeof dp);
  For(i,1,n) num[i] = k[n-i+1] - '0';
  return dfs(n, 0, 1);
}

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> k >> d;
  n = k.size();
  k = " " + k;
  cout << (ans()-1+mod)%mod << '\n';
  return 0;
}

T

数数 dp。

\(dp_{i,j}\) 表示前 \(i\) 位填 \(1\)\(i\),当前位填 \(j\) 的方案数。

\(s_i='>'\),当前位填的数要大于 \(i\),所以 \(dp_{i,j}=\sum\limits_{k=j}^{i-1}dp_{i-1,k}\)

\(s_i='<'\),当前位填的数要小于 \(i\),所以 \(dp_{i,j}=\sum\limits_{k=1}^{j-1}dp_{i-1,k}\)

答案为 \(\sum\limits_{i=1}^n dp_{n,i}\)

使用前缀和优化即可。

时间复杂度 \(O(n^2)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 1000000007

using namespace std;

const int N = 3e3 + 10;

int n, dp[N][N], sum[N][N];

char s[N];

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  cin >> s + 2;
  dp[1][1] = sum[1][1] = 1;
  For(i,2,n) {
    For(j,1,i) {
      if(s[i] == '<') dp[i][j] = sum[i-1][j-1];
      else dp[i][j] = (sum[i-1][i-1] - sum[i-1][j-1] + mod) % mod;
      sum[i][j] = (sum[i][j-1] + dp[i][j]) % mod;
    } 
  }
  int ans = 0;
  For(i,1,n) ans = (ans + dp[n][i]) % mod;
  cout << ans << '\n';
  return 0;
}

U

看题解做出来的...

状压 dp。

\(dp_S\) 表示划分状态 \(S\) 的最大值。可以预处理出 \(val_S\) 表示状态 \(S\) 的贡献。

则转移为:

\(dp_S=\max\limits_{j\in{i的子集}} dp_{S \oplus j}+dp_{j}\)

答案为:\(dp_{2^n-1}\)

时间复杂度为 \(O(n^2 2^n+3^n)\)。可过。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)

using namespace std;

const int N = 17, M = (1<<17);

int n, a[N][N], val[M], dp[M];

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  For(i,0,n-1) For(j,0,n-1) cin >> a[i][j];
  For(S,0,(1<<n)-1) {
    For(i,0,n-1) {
      For(j,0,i-1) {
        if(((S >> i) & 1) && ((S >> j) & 1)) val[S] += a[i][j];
      }
    }
  }
  For(S,0,(1<<n)-1) {
    dp[S] = val[S];
    for (int j = S; j; j = S & (j - 1)) {
      dp[S] = max(dp[S], dp[S^j] + dp[j]);
    }
  }
  cout << dp[(1<<n)-1] << '\n';
  return 0;
}

V

W

X

贪心dp。

可以想到做 01 背包,但是转移拓扑序会出问题,以至于我们无法确定转移顺序。

考虑通过贪心确定转移顺序。直接按 \(w\)\(s\) 排序肯定是错误的。从下往上,考虑到决策至相同塔高时,剩余能放的重量最多方案的肯定最优。对于相邻的 \(i,j\) 箱子。\(s_i-w_j\) 表示 \(j\) 在上,\(i\) 在下能剩余的放置重量,\(s_j-w_i\) 表示 \(i\) 在上,\(j\) 在下能剩余的放置重量。所以 \(s_i-w_j<s_j-w_i\) 则交换 \(i,j\)

然后按照顺序做背包即可。时间复杂度 \(O(n\log n+nm)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)

using namespace std;

const int N = 1e3 + 10, M = 1e5 + 10;

struct Node {
  int w, s, v;
} a[N];

int n, dp[M], ans;

bool cmp(Node x, Node y) {
  return x.w + x.s < y.w + y.s;
}

signed main() {
  cin >> n;
  For(i,1,n) cin >> a[i].w >> a[i].s >> a[i].v;
  sort(a + 1, a + n + 1, cmp);
  dp[0] = 0;
  For(i,1,n) {
    FOR(j,a[i].s,0) {
      dp[j + a[i].w] = max(dp[j + a[i].w], dp[j] + a[i].v); 
    }
  }
  For(i,0,M-1) ans = max(ans, dp[i]);
  cout << ans << '\n';
  return 0;
}

Y

Z

posted @ 2024-10-10 15:25  Daniel_yzy  阅读(13)  评论(0编辑  收藏  举报