动态规划(Atcoder DP 26题)

Atcoder DP contest 题解

Frog 1

$N$ 个石头,编号为 $1,2,...,N$。对于每个 $i(1 \leq i \leq N)$,石头 $i$ 的高度为 $h_i$。
最初有一只青蛙在石头 $1$ 上。他将重复几次以下操作以到达石头 $N$:
如果青蛙当前在石头 $i$ 上,则跳到石头 $i+1$ 或石头 $i+2$需要 $|h_i - h_j|$ 的费用,而 $j$ 是要落到上面的石头。
找到青蛙到达石头 $N$ 之前需要的最小总费用。

  • $2 \le N\ \le 10^5$
  • $1 \le h_i\ \le 10^4$

这是一个线性动态规划,在这题中,阶段被定义为青蛙正处在的格子,每次的决策就是跳 1 格或者 2 格。

定义:f[i] 表示跳到第 i 个格子的最小总费用。

转移:f[i]=min(f[i-1]+abs(h[i-1]-h[i]),f[i-2]+abs(h[i-2]-h[i]));

当然,$i\le 2$ 时需要特判。

Frog 2

题意基本和上一题相同,不过这题能跳 K 格。我们只需把上一题的转移改成循环的格式即可。

Vacation

太郎的暑假有$n$天,第$i$天他可以选择做以下三种事情:

  1. 游泳,获得$a_i$点幸福值。
  2. 捉虫,获得$b_i$点幸福值。
  3. 写作业,获得$c_i$点幸福值。

但他不能连续两天进行同一种活动,请求出最多可以获得多少幸福值。

  • $1\ \leq\ N\ \leq\ 10^5$
  • $1\ \leq\ a_i,\ b_i,\ c_i\ \leq\ 10^4$

很明显的阶段是天数,每天的决策也很简单,唯一限制我们的是“不能连续两天进行同一种活动”的要求,我们在动态规划的状态里还没表示出来。进一步发现,我们只需要相邻两天的活动,这启示我们给 f 数组加一维。

定义:f[i][0/1/2] 表示第 i 天,当天选择了某种活动的最大幸福值。

转移:if(j!=k) f[i][j]=max(f[i][j],f[i-1][k]+a[i][j]);

最终答案就是$\max{f_{n,0},f_{n,1},f_{n,2}}$。

Knapsack 1

01 背包问题。

Knapsack 2

题意同上,但是数据范围:

  • $1\ \leq\ N\ \leq\ 100$
  • $1\ \leq\ W\ \leq\ 10^9$
  • $1\ \leq\ w_i\ \leq\ W$
  • $1\ \leq\ v_i\ \leq\ 10^3$

如果把 W 作为动态规划的阶段显然空间复杂度过大,所以考虑把 W 作为动态规划的答案。

定义:f[i][j] 表示前 i 个物品得到 j 的价值所需最小重量。

转移:f[i][j]=min(f[i-1][j],f[i-1][j-v[i]]+w[i]);

统计答案时倒序枚举 j,当第一次出现 f[n][j]<=W 时,输出答案。

此题亦可压维:

for(int i=1;i<=n;i++)
for(int j=sv;j>=v[i];j--)
f[j]=min(f[j],f[j-v[i]]+w[i]);
for(int i=sv;i>=0;i--)
if(f[i]<=W){
cout<<i<<endl;break;
}

LCS

基础的 LCS 问题,区别是要输出一种方案,可以采取记录从哪里来的方法,最后逆序递归一次即可。

const int N=3005,M=(N<<1),inf=0x3f3f3f3f;
int n,m,f[N][N];
Pair p[N][N];
string x,y;
void print(int i,int j){
Pair now=p[i][j];
if(now.first&&now.second) print(now.first,now.second);
if(i-now.first==1&&j-now.second==1)
cout<<x[i-1];
}
int main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>x>>y;
n=x.size();m=y.size();
F(i,1,n){
F(j,1,m){
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(f[i-1][j]>f[i][j-1]) p[i][j]={i-1,j};
else p[i][j]={i,j-1};
if(x[i-1]==y[j-1]) {
if(f[i-1][j-1]+1>f[i][j]){
f[i][j]=f[i-1][j-1]+1;
p[i][j]={i-1,j-1};
}
}
}
}
print(n,m);
return 0;
}

Longest Path

求有向无环图上的最长路长度。

做一个拓扑排序,顺便统计答案即可。

const int N=100005,M=(N<<1),inf=0x3f3f3f3f;
int n,m,outd[N],f[N];
vector<int> G[N];
int main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m;
F(i,1,m){
int u,v;
cin>>u>>v;
G[v].push_back(u);
outd[u]++;
}
queue<int> q;
F(i,1,n)
if(!outd[i])
q.push(i);
while(q.size()){
int now=q.front();
q.pop();
for(auto i:G[now]){
f[i] = max(f[i],f[now]+1);
if(!(--outd[i])) q.push(i);
}
}
cout<<*max_element(f+1,f+n+1);
return 0;
}

Grid 1

参见NOIP过河卒即可。

Coins

期望DP。

定义:f[i][j]表示前 i 个硬币有 j 个正面朝上的概率。

转移:f[i][j]=f[i-1][j-1]*p[i]+f[i-1][j]*(1-p[i]);

初始化:f[0][0]=1;

Sushi

这题写了题解。

Part 1 题意分析

有 $n$ 个盘子,每次随机选择一个盘子,吃掉其中的一个寿司。若没有寿司则不吃。

求最后吃掉所有寿司的期望步数。

题意告诉我们非常重要的一点,可以选择空盘子,也就是说这些浪费的步数也会算入期望里。

Part 2 状态转移方程

定义:$f_{i,j,k}$ 表示有 $i$ 个盘子里有一个寿司,$j$ 个盘子里有两个寿司,$k$ 个盘子里有三个寿司时吃完所有寿司的期望。

初始化:$f_{0,0,0}=0$。

接下来,列出最初的方程:

<script type="math/tex;mode=inline">f_{i,j,k}=\dfrac{n-i-j-k}{n}\times f_{i,j,k}+\dfrac{i}{n}\times f_{i-1,j,k}+\dfrac{j}{n}\times f_{i+1,j-1,k}+\dfrac{k}{n}\times f_{i,j+1,k-1}+1</script>f_{i,j,k}=\dfrac{n-i-j-k}{n}\times f_{i,j,k}+\dfrac{i}{n}\times f_{i-1,j,k}+\dfrac{j}{n}\times f_{i+1,j-1,k}+\dfrac{k}{n}\times f_{i,j+1,k-1}+1

首先看第一项,$\dfrac{n-i-j-k}{n}\times f_{i,j,k}$ 表示有 $\dfrac{n-i-j-k}{n}$ 的概率选到空盘子,吃完空盘子之后,会发现局面没有任何变化,现在想吃完所有寿司仍然需要 $f_{i,j,k}$ 的期望步数。

第二项,这次选择吃有一个寿司的盘子,吃完后下一步里有一个寿司的盘子就少了一个,到达了 $f_{i-1,j,k}$ 的局面,选中这个盘子的概率同上,不再解释。

第三项,这次选择吃有两个寿司的盘子,吃完后发现,两个寿司的盘子少了一个,可是剩下的那个寿司就成为了一个寿司的盘子了,所以到达了 $f_{i+1,j-1,k}$ 的局面。

最后解释一下最后一个加一的含义,注意到我前面特别标粗了吃完两个字,意思就是我们目前加的都是吃完后的期望,难道当前吃的这一步不需要步数吗?所以这个一表示的就是当前吃的这一步!

理解了初代方程之后,我们就可以合并同类项来化简了。

<script type="math/tex;mode=inline">\dfrac{i+j+k}{n}\times f_{i,j,k}=\dfrac{i}{n}\times f_{i-1,j,k}+\dfrac{j}{n}\times f_{i+1,j-1,k}+\dfrac{k}{n}\times f_{i,j+1,k-1}+1</script>\dfrac{i+j+k}{n}\times f_{i,j,k}=\dfrac{i}{n}\times f_{i-1,j,k}+\dfrac{j}{n}\times f_{i+1,j-1,k}+\dfrac{k}{n}\times f_{i,j+1,k-1}+1

<script type="math/tex;mode=inline">(i+j+k)\times f_{i,j,k}=i\times f_{i-1,j,k}+j\times f_{i+1,j-1,k}+k\times f_{i,j+1,k-1}+n</script>(i+j+k)\times f_{i,j,k}=i\times f_{i-1,j,k}+j\times f_{i+1,j-1,k}+k\times f_{i,j+1,k-1}+n

<script type="math/tex;mode=inline">f_{i,j,k}=\dfrac{i}{i+j+k}\times f_{i-1,j,k}+\dfrac{j}{i+j+k}\times f_{i+1,j-1,k}+\dfrac{k}{i+j+k}\times f_{i,j+1,k-1}+\dfrac{n}{i+j+k}</script>f_{i,j,k}=\dfrac{i}{i+j+k}\times f_{i-1,j,k}+\dfrac{j}{i+j+k}\times f_{i+1,j-1,k}+\dfrac{k}{i+j+k}\times f_{i,j+1,k-1}+\dfrac{n}{i+j+k}

Part 3 代码

这题如果需要循环转移的话需要按照 $k,j,i$ 的顺序,相较之下记忆化搜索不用多想。

const int N=305,M=(N<<1),inf=0x3f3f3f3f;
double f[N][N][N];
double dp(int i,int j,int k){
if(i==0&&j==0&&k==0) return 0;
if(f[i][j][k]) return f[i][j][k];
double p=n*1.0/(i+j+k);
if(i) p+=i*1.0/(i+j+k)*dp(i-1,j,k);
if(j) p+=j*1.0/(i+j+k)*dp(i+1,j-1,k);
if(k) p+=k*1.0/(i+j+k)*dp(i,j+1,k-1);
return f[i][j][k]=p;
}
int main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n,x,c[4]={0};cin>>n;
F(i,1,n){
cin>>x;
c[x]++;
}
cout<<fixed<<setprecision(10)<<dp(c[1],c[2],c[3]);
return 0;
}

Stones

可行性DP。

定义:f[i]表示 i 个石头是先手还是后手赢。

一句话转移:F(i,0,m)F(j,1,n) f[i+a[j]]+=!f[i];

Deque

区间DP。

定义:f[l][r]表示l到r之间的X-Y。

区间DP转移时有一个小转化,就是先手尽可能大就是X-Y大,后手的反而小。

dF(i,n,1) opt[i]=opt[i+1]^1;
F(len,1,n){
for(int l=1,r=l+len-1;r<=n;l++,r++){
if(opt[len]) f[l][r]=max(f[l][r-1]+a[r],f[l+1][r]+a[l]);
else f[l][r]=min(f[l][r-1]-a[r],f[l+1][r]-a[l]);
}
}

注意要判断当前是先手还是后手。

Candies

前缀和优化 DP。

普通的定义:f[i][j] 表示前 i 个小朋友发 j 个糖的方案数量。

套路的转移:

F(i,1,n)F(j,0,m)
F(k,max(j-a[i],0),j)
f[i][j]+=f[i-1][k];

但是这样的话时间复杂度就超标了,注意到我们每次都是加上了连续的一段,那么就可以用前缀和优化掉。

F(i,1,n){
F(j,1,m) (f[i-1][j]+=f[i-1][j-1])%=mod;
F(j,0,m){
if(max(0,j-a[i])>0) f[i][j]=((f[i-1][j]-f[i-1][max(0,j-a[i])-1])%mod+mod)%mod;
else f[i][j]=f[i-1][j];
}
}

注意负数取模。

算法学习笔记【4】| 动态规划(Atcoder DP 26题)的更多相关文章

  1. 【学习笔记】动态规划—各种 DP 优化

    [学习笔记]动态规划-各种 DP 优化 [大前言] 个人认为贪心,\(dp\) 是最难的,每次遇到题完全不知道该怎么办,看了题解后又瞬间恍然大悟(TAT).这篇文章也是花了我差不多一个月时间才全部完成 ...

  2. 【算法学习笔记】动态规划与数据结构的结合,在树上做DP

    前置芝士:Here 本文是基于 OI wiki 上的文章加以修改完成,感谢社区的转载支持和其他方面的支持 树形 DP,即在树上进行的 DP.由于树固有的递归性质,树形 DP 一般都是递归进行的. 基础 ...

  3. 【学习笔记】动态规划—斜率优化DP(超详细)

    [学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...

  4. 算法学习笔记(20): AC自动机

    AC自动机 前置知识: 字典树:可以参考我的另一篇文章 算法学习笔记(15): Trie(字典树) KMP:可以参考 KMP - Ricky2007,但是不理解KMP算法并不会对这个算法的理解产生影响 ...

  5. C / C++算法学习笔记(8)-SHELL排序

    原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...

  6. Manacher算法学习笔记 | LeetCode#5

    Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...

  7. 汇编入门学习笔记 (七)—— dp,div,dup

    疯狂的暑假学习之  汇编入门学习笔记 (七)--  dp.div.dup 參考: <汇编语言> 王爽 第8章 1. bx.si.di.和 bp 8086CPU仅仅有4个寄存器能够用 &qu ...

  8. 「学习笔记」wqs二分/dp凸优化

    [学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...

  9. Johnson算法学习笔记

    \(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...

  10. 某科学的PID算法学习笔记

    最近,在某社团的要求下,自学了PID算法.学完后,深切地感受到PID算法之强大.PID算法应用广泛,比如加热器.平衡车.无人机等等,是自动控制理论中比较容易理解但十分重要的算法. 下面是博主学习过程中 ...

随机推荐

  1. Oracle 中UNDO与REDO的区别详解

    一 为了更清楚的看出2者区别,请看下表: UNDO                                                                   REDO Rec ...

  2. duilib 入坑

    记录 duilib 开发遇到的问题 当前最新的 duilib 版本更新是在  2019-4-28-2,从 vcpkg 查询得知 我的机器是 windows 10,vs2019 我是从 duilib 库 ...

  3. redis7源码分析:redis 单线程模型解析,一条get命令执行流程

    有了下文的梳理后 redis 启动流程 再来解析redis 在单线程模式下解析并处理客户端发来的命令 1. 当 client fd 可读时,会回调readQueryFromClient函数 void ...

  4. django中修改QueryDict数据类型和转成普通字典

    修改QueryDict的几种方式 简介 在正常的请求/响应周期中访问时,request.POST和request.GET上的QueryDict将是不可变的. 要获得可变版本,您需要使用QueryDic ...

  5. jq中的正则

    正则匹配表达式 \w \s \d \b . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 等价于 '[A-Za-z0-9_]'. \s 匹配任意的空白符 \d 匹配数字 \b 匹配单 ...

  6. 阿尔萨斯(Arthas)入门

    目录 简介 Arthas(阿尔萨斯) 能为你做什么 安装 快速安装 全量安装 卸载 使用 启动arthas 查看dashboard 通过thread命令来获取到arthas-demo进程的Main C ...

  7. BeanShell Sampler 前置处理器

    一概念: 前置处理器主要作用: 用于修改即将发送的http的请求数据 BeanShell预处理器可以在取样器发送请求之前被执行,可以通过它完成发送请求所需的数据 其中的ctx.vars.props.p ...

  8. 通过 TCPView KPKIService.exe 删掉 (原来是单点登录的中间件)

    叫 统一安全中间件,就是个第三方做的key的安全检查,谁知道是哪年装的 (原来是单点登录的中间件) 资料 https://baijiahao.baidu.com/s?id=17173842191483 ...

  9. aardio 嵌入 其他应用程序

    aardio 嵌入 其他应用程序 需求 这个chrome壳不能进行拖拽和缩放,所以再套一个壳,可以再分屏的时候用 import win.ui; /*DSG{{*/ winform = win.form ...

  10. vscode git冲突 1. git stash 2. 更新代码 3. git stash pop 4.提交代码

    vscode git冲突 1. git stash 2. 更新代码 3. git stash pop 4.提交代码