动态规划(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. vscode添加自定义html片段

    最近在学vue,用的是微软的vscode 开发工具. 很不错,赞一下微软.里面包含了众多插件大家可以各取所需. 另外有一项实用的功能,User Snippets 用户自定义代码段, 对于那些需要重复编 ...

  2. P4141 消失之物题解(写给每一位与我一样的新手玩家)

    消失之物 传送门: P4141 消失之物 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路 暴力稳了 但是 hack tle了 这时候我们要想办法优化 这是一个回退背包问题 首先 ...

  3. TCP/IP的确认号,序列号和超时重传的学习笔记

    一:确认应答和序列号 在tcp中,发送端的数据到达主机时,接收端会返回一个已收到的通知.这个消息叫做确认应答(ACK). 当发送端发送数据后,会等待对端的确认应答.如果有确认应答,说明数据已经成功到达 ...

  4. leetcode - 相同的树

    给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同. 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的. 示例 1: 输入:p = [1,2,3], q = [1 ...

  5. 前端面试题(四)—— 事件委托(Event Delegation)

    一.什么是事件委托 事件委托(Event Delegation)是一种常用的技术. 它利用事件冒泡的特性,在父元素上监听事件,而不是在子元素上直接添加事件监听器. 通过在父元素上捕获事件,然后根据事件 ...

  6. 用宝塔设立分发Directory.Build.props及其Import文件的网站

    新建站点 服务器名称我们约定是dev.amihome.cn 创建的默认站点有下面4个文件 把本地的文件,用宝塔上传 上图我们是把本地的Directory.Build.props文件上传到了网站的根目录 ...

  7. 第一百一十三篇: JS数组Array(二)数组方法 栈、队列、排序

    好家伙,    在上一篇中,我们知道了, JS的数组中每个槽位可以存储任意类型的数据 那么,我们能通过数组去模仿某些数据结构吗? 答案是肯定的 1.栈方法 ECMAScript 给数组提供几个方法,让 ...

  8. 【Azure 服务总线】查看Service Bus中消息多次发送的日志信息,消息是否被重复消费

    问题描述 使用Service Bus,发现消息被重复消费.如果要查看某一条消息的具体消费情况,需要那些消息的属性呢? 问题解答 使用Azure Service Bus,当消费发送到服务端后,就会生产相 ...

  9. 【Azure 事件中心】Event Hub 消费端出现 Timeout Exception,errorContext中 LINK_CREDIT为0的解释

    问题描述 在使用Event Hub SDK消费数据过程中,出现大量的Timeout Exception,详细消息为: com.microsoft.azure.eventhubs.TimeoutExce ...

  10. 浅入Kubernetes(4):使用Minikube体验

    Minikube 打开 https://github.com/kubernetes/minikube/releases/tag/v1.19.0 下载最新版本的二进制软件包(deb.rpm包),再使用 ...