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

A#

B#

C#

D#

E#

F#

G#

H#

I#

概率 dp。

dpi,j 表示前 i 个硬币中有 j 个正面的概率。转移显然:

dpi,j=dpi1,j1×pi+dpi1,j×(1pi)

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

dpi,j=dpi1,j×(1pi)

初始化 dp0,0=1

时间复杂度 O(n2)

点击查看代码
#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。

dpi 表示剩下 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。

dpi,j 表示前 i 个人分 j 块糖果的方案数。转移为:

dpi,j=x=0min(j,ai)dpi1,jx=x=jmin(j,ai)jdpi1,x

答案为 dpn,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。

dpi,S 表示左部点前 i 个点完全匹配,右部点状态为 S 的方案数。转移为:

dpi,S=to(i,j)dpi1,Sj,其中 Sj 表示 S 状态中去掉 j 的状态。同时要保证 S 状态中 j 的状态为 1

答案为 dpn,2n1

时间复杂度 O(n22n),卡常可过。

点击查看代码
#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,类似 没有上司的舞会

dpx,0/1 表示 x 节点为白或黑点的方案数。转移为:

dpx,0=yson(x)(dpy,0+dpy,1)
dpx,1=yson(x)dpy,0

答案为 dp1,0+dp1,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#

带权最长上升子序列

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

dpi=maxj=1i1dpj+ai

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

时间复杂度 O(nlogn)

点击查看代码
#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。

dpk,i 表示经过长度为 k,当前从某点转移至 i 的方案数。转移有:

dpk,i=to(j,i)dpk1,j

答案为 i=1ndpk,i

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

设计矩阵

[a1,1a2,1an,1a1,2a2,2an,2a1,na2,nan,n]

对此矩阵进行 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。

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

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

上限的判定为从高位至低位的前缀一致(K 的前缀)。当达到上限时,maxxK 的第 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。

dpi,j 表示前 i 位填 1i,当前位填 j 的方案数。

si=>,当前位填的数要大于 i,所以 dpi,j=k=ji1dpi1,k

si=<,当前位填的数要小于 i,所以 dpi,j=k=1j1dpi1,k

答案为 i=1ndpn,i

使用前缀和优化即可。

时间复杂度 O(n2)

点击查看代码
#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。

dpS 表示划分状态 S 的最大值。可以预处理出 valS 表示状态 S 的贡献。

则转移为:

dpS=maxjidpSj+dpj

答案为:dp2n1

时间复杂度为 O(n22n+3n)。可过。

点击查看代码
#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 背包,但是转移拓扑序会出问题,以至于我们无法确定转移顺序。

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

然后按照顺序做背包即可。时间复杂度 O(nlogn+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#

作者:Daniel-yao

出处:https://www.cnblogs.com/Daniel-yao/p/18449374

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Daniel_yzy  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示