Atcoder DP contest 题解
动态规划(Atcoder DP 26题)
- on Atcoder
- on Luogu
- 本文同步发表于知乎专栏。
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$天他可以选择做以下三种事情:
- 游泳,获得$a_i$点幸福值。
- 捉虫,获得$b_i$点幸福值。
- 写作业,获得$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$。
接下来,列出最初的方程:
$$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}$ 的局面。
最后解释一下最后一个加一的含义,注意到我前面特别标粗了吃完两个字,意思就是我们目前加的都是吃完后的期望,难道当前吃的这一步不需要步数吗?所以这个一表示的就是当前吃的这一步!
理解了初代方程之后,我们就可以合并同类项来化简了。
$$\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$$
$$(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$$
$$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];
}
}
注意负数取模。
Atcoder DP contest 题解的更多相关文章
- Atcoder Educational DP Contest 题解
A - Frog 1/B - Frog 2 入门... #include<cstdio> #define abs(a) ((a)>=0?(a):(-(a))) #define min ...
- AtCoder Beginner Contest 154 题解
人生第一场 AtCoder,纪念一下 话说年后的 AtCoder 比赛怎么这么少啊(大雾 AtCoder Beginner Contest 154 题解 A - Remaining Balls We ...
- AtCoder Beginner Contest 153 题解
目录 AtCoder Beginner Contest 153 题解 A - Serval vs Monster 题意 做法 程序 B - Common Raccoon vs Monster 题意 做 ...
- KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解
KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解 哦淦我已经菜到被ABC吊打了. A - Century 首先把当前年 ...
- AtCoder Beginner Contest 184 题解
AtCoder Beginner Contest 184 题解 目录 AtCoder Beginner Contest 184 题解 A - Determinant B - Quizzes C - S ...
- AtCoder Beginner Contest 173 题解
AtCoder Beginner Contest 173 题解 目录 AtCoder Beginner Contest 173 题解 A - Payment B - Judge Status Summ ...
- AtCoder Beginner Contest 169 题解
AtCoder Beginner Contest 169 题解 这场比赛比较简单,证明我没有咕咕咕的时候到了! A - Multiplication 1 没什么好说的,直接读入两个数输出乘积就好了. ...
- AtCoder Beginner Contest 238 A - F 题解
AtCoder Beginner Contest 238 \(A - F\) 题解 A - Exponential or Quadratic 题意 判断 \(2^n > n^2\)是否成立? S ...
- AtCoder Regular Contest 094 (ARC094) CDE题解
原文链接http://www.cnblogs.com/zhouzhendong/p/8735114.html $AtCoder\ Regular\ Contest\ 094(ARC094)\ CDE$ ...
- AtCoder Beginner Contest 177 题解
AtCoder Beginner Contest 177 题解 目录 AtCoder Beginner Contest 177 题解 A - Don't be late B - Substring C ...
随机推荐
- 【快速排序】采用D&C(divide and conquer)方法求解
介绍 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists).平均状况下,排序 n 个项目要 Ο(nlogn) 次比较,在最坏状况下 ...
- PL/SQL相关的数据字典
PL/SQL相关的数据字典 http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62plsql-1851968.html 有时候, ...
- QT 无法识别某些字体导致程序启动失败
有用户反馈启动程序时,没有出现 UI 界面,程序跟 "闪退了一样",查看日志,没有发现闪退或者报错异常,后面远程用户电脑并尝试解决 研究分析:在用户电脑上运行 debug 包,会出 ...
- win32 - WM_DROPFILES的用法
WM_DROPFILES: 当用户将文件拖放到已注册为丢弃文件的接收者的应用程序窗口中时发送该消息 我们可以利用这个消息获取文件名称,并将它们保存到容器里. LRESULT CALLBACK Stat ...
- MySQL重新设置auto_increment值
需求描述 通常,我们都会在数据库表中设置一个自增字段作为主键,该字段的值会随着添加新记录而自增. 同时也必须注意,这个自增字段的值只会一直增加,即使把记录删除了,该自增字段的值也不会变小. 因此,就会 ...
- Lua学习笔记之迭代器、table、模块和包、元表和协程
迭代器 迭代器是一种对象,它能够来遍历标准库模板容器中的部分或全部元素,每个迭代器对象代表容器中确定的地址,在Lua中迭代器是一种支持指针类型的结构,他可以遍历集合的每一个元素. 泛型for迭代器 泛 ...
- SpringMvc原理概述
目录 MVC整体架构和流程 SpringMVC 框架组件概述 SpringMVC 配置详解 springmvc.xml MVC整体架构和流程 用户发送请求至前端控制器 DispatcherServle ...
- Jupyter Notebook 遇上 NebulaGraph,可视化探索图数据库
在之前的<手把手教你用 NebulaGraph AI 全家桶跑图算法>中,除了介绍了 ngai 这个小工具之外,还提到了一件事有了 Jupyter Notebook 插件: https:/ ...
- Jmeter json断言的使用
1 添加方式:取样器右键->添加->断言->JSON断言 作用:使用JSON表达式提取实际数据与预期进行比较 2首先我们来了解下断言组件的各个功能: Asset JSON Pat ...
- 标准差为什么除以n-1
参考:https://blog.csdn.net/qian2213762498/article/details/80558018 如果要测量中国人的平均身高,假设为μ,通常会随机取假设10000人,求 ...