洛谷 动态规划一日游 P2577、P1070、P2051

2018年3月19日

贼颓呢,一天就写了两道DP,还都不会写,这可GG。
动态规划真的难且有趣,算法题中动态规划占到了很大的比例,而且动态规划往往是辅助解决一些其他类型问题的基础,加深加强对动态规划问题的认识和训练非常有必要。

P2577 午餐

题意

题意见题目链接

题解

这道题目本质上是一道背包问题,是背包问题的变形,这道题不同的地方在于有两个背包。
因此,我们设计状态的时候,两个背包的状态都要记录。

贪心

由于每个队列的排队顺序不一样,结果也是不一样的。而可以证明,当把所有的同学按照其吃饭时间降序排序的话,这样的排队顺序一定是最优的顺序。

朴素

我们最初想到的状态是这样的:
dp[i][j][k]dp[i][j][k]表示当前考虑的是前i个人已经被分配,且第一个队列排队打饭时间和为i,第二个队列排队打饭的时间和为j,最早的集合时间。
那么转移方程可以写成
dp[i][j][k]=min(max(dp[i1][ja[i]][j],j+b[i]),max(dp[i1][j][ka[i]],k+b[i]))dp[i][j][k]=min(max(dp[i−1][j−a[i]][j],j+b[i]),max(dp[i−1][j][k−a[i]],k+b[i]))
转移方法:把i分给第1个窗口或者把i分给第2个窗口。

显然这样时间、空间复杂度会爆炸。
空间复杂度为O(2005)O(2005)

优化

因此我们必须优化,进一步挖掘条件以降维。
我们发现j+k=sumb[i]j+k=sumb[i]
这样的话,我们直接可以减少一维,定义
dp[i][j]dp[i][j]表示考虑前i个人,第一个窗口的同学总打饭时间为j时候,前i个同学最早集合的时间。
空间复杂度变成了O(2003)O(2003)
转移方程:
dp[i][j]=min(max(dp[i1][ja[i]],j+b[i]),max(dp[i1][j],sumb[i]j+b[i]))dp[i][j]=min(max(dp[i−1][j−a[i]],j+b[i]),max(dp[i−1][j],sumb[i]−j+b[i]))

进一步优化

由于我们发现i状态的转移只与i-1有关系,因此,我们可以用滚动数组继续优化掉一维。
时间复杂度O(2002)O(2002)
总的时间复杂度为O(2003)O(2003)

细节

注意边界条件以及转移成立的条件。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef pair<int,int> pii;
const int maxn = 205;
const int inf = 0x3f3f3f3f;
pii ps[maxn];
int dp[2][maxn*maxn],sum[maxn];
int n;
int main(){memset(dp,0x3f,sizeof(dp));scanf("%d",&n);for(int i = 0;i < n;++i){int a,b;scanf("%d%d",&a,&b);ps[i+1] = make_pair(-b,a);}sort(ps+1,ps+1+n);dp[0][0] = 0;for(int i = 1;i <= n;++i){memset(dp[i&1],0x3f,sizeof(dp[i&1]));sum[i] = sum[i-1] + ps[i].second;for(int j = 0;j <= sum[i];++j){if(j >= ps[i].second)dp[i&1][j] = min(dp[i&1][j],max(dp[(i-1)&1][j-ps[i].second],j-ps[i].first));if(sum[i]-j >= ps[i].second)dp[i&1][j] = min(dp[i&1][j],max(dp[(i-1)&1][j],sum[i]-j-ps[i].first));}}int mi= inf;for(int j = 0;j <= sum[n];++j){if(dp[n&1][j] < mi){mi = dp[n&1][j];}}cout<<mi<<endl;return 0;
}

P1070道路游戏

题意

题目链接看题意

题解

这道题本质上就是在下图斜线上的dp。

Column表示机器人出售站。
Row表示时间轴。
字母相同的斜线上相邻的两个表格表示由上一个表格的数字,下一步将取到下面表格的数字。
这样就相当于我们把整个表格横着切几段,然后每一段内部选一条连续的斜线,并把斜线里的数字加起来减去该段斜线第一个站点的费用。把所有的段的值加起来得到一个新的数值,我们要做的就是最大化这个数值。
定义状态:
opt[i]opt[i]表示前i行所能收集的最大金币值。
sum[i][j]sum[i][j]表示从第一行的第i站出发,沿着斜线走,一共收集j格的金币得到的总和。

那么状态转移方程就是
ii表示行,j表示机器人购买点所在的斜线编号。
kk表示上一次刚好停在哪一行。
opt[i]=max(opt[k]+sum[j][i]sum[j][k]cost[j+k]),1<=k<=p
解释为什么costcost的下标是j+kj+k:

因为j表示的斜线编号,在第k+1行买机器人,实际的购买站点是j+k

这样做的话,时间复杂度是O(nmp)O(n∗m∗p)不满足要求,因此我们必须继续优化。
看着形式,我就只能想到单调队列或者是斜率优化了,这道题单调队列优化显然是可以的。
我们i,j循环变量不能省略,能省略的就只有k了。我们把i和j与k分离开:
opt[i]=max(sum[j][i]+(opt[k]sum[j][k]cost[j+k])),1<=k<=popt[i]=max(sum[j][i]+(opt[k]−sum[j][k]−cost[j+k])),1<=k<=p
分离出来的这部分:
(opt[k]sum[j][k]cost[j+k])(opt[k]−sum[j][k]−cost[j+k])
就只与kkj有关了,因此,我们可以建立nn个单调队列对应j,队列内部对应kk

dp核心代码

for(int i = 1;i <= m;++i){//i表示时刻for(int j = 1;j <= n;++j){//j表示机器人的购买点所在的斜线编号while(PQS[j].size() && i-PQS[j].getfront().first > p) PQS[j].pop();pii p = PQS[j].getfront();opt[i] = max(opt[i],sum[j][i] + p.second);}for(int j = 1;j <= n;++j){int pre = j+i;while(pre > n) pre -= n;PQS[j].push(i,opt[i] - sum[j][i] - cost[pre]);}}

总的代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1005;
typedef pair<int,int> pii;struct PQ{pii ps[maxn];int front,tail;void push(int idx,int key){while(tail > front && key >= ps[tail-1].second)tail--;ps[tail++] = make_pair(idx,key);}void pop(){if(front < tail) ++front;}int size(){return tail-front;}pii getfront(){return ps[front];}
}PQS[maxn];int n,m,p;
int val[maxn][maxn];
int sum[maxn][maxn];//1:出发点 2: 步数
int opt[maxn];
int cost[maxn];
const int inf = 1e9;
int main(){scanf("%d%d%d",&n,&m,&p);for(int i = 1;i <= n;++i){for(int j = 1;j <= m;++j){scanf("%d",&val[i][j]);}}for(int i = 1;i <= n;++i){scanf("%d",&cost[i]);}for(int i = 1;i <= n;++i){int pre = i-1;for(int j = 1;j <= m;++j){if(++pre == n+1) pre = 1;sum[i][j] = sum[i][j-1]+val[pre][j];//printf("i:%d,j:%d,sum:%d\n",i,j,sum[i][j]);}}for(int i = 1;i <= n;++i)PQS[i].push(0,-cost[i]); for(int i = 1;i <= m;++i)opt[i] = -inf;//dpfor(int i = 1;i <= m;++i){//i表示时刻for(int j = 1;j <= n;++j){//j表示机器人购买点所在的斜线编号while(PQS[j].size() && i-PQS[j].getfront().first > p) PQS[j].pop();pii p = PQS[j].getfront();opt[i] = max(opt[i],sum[j][i] + p.second);}for(int j = 1;j <= n;++j){int pre = j+i;while(pre > n) pre -= n;PQS[j].push(i,opt[i] - sum[j][i] - cost[pre]);}}cout<<opt[m]<<endl;return 0;
}

P2051 中国象棋

题意

点击链接查原题目

题解

我好菜啊,这道是原题,暑假集训的时候做过,而且当时还独自做出来了,过了半年,自己再做的时候发现竟然不会做了,看了题解的提示才想出来。咋越练越菜呢???

这道题目关键点在状态的定义!

我们需要把无用的信息都去除掉,在状态定义的时候尽量捕捉最本质、最关键的信息。
例如本题,按行考虑,在考虑到第i行的时候,我们关注点在前i行中,形成的0炮列有几个,1个炮的列有几个,2个跑的列有几个,这样。
而我们不需要知道具体的前i行的棋子排列。

状态定义就出来了:
dp[i][j][k]表示前i行,构成有j个1炮列、k个2炮列可行的方案数。
这样的话,转移方程也很容易得到,就是乘法原理。

注意分类的时候这样分:
第i行不加棋子的转移、加1个棋子的转移、加2个棋子的转移。

加1个棋子的转移又可以分为:把这个棋子加到0炮列,把这个棋子加到1炮列。

加2个棋子的转移又可以分为:全都加到0炮列、全都加到1炮列、0炮列1炮列分别加一个。

按照这样分类转移,转移方程很好写。

代码

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
ll n,m;
const int maxn = 106;
const ll mod = 9999973;
ll dp[maxn][maxn][maxn];
int main(){cin>>n>>m;dp[1][0][0] = 1;dp[1][1][0] = m;dp[1][2][0] = m*(m-1)/2;for(int i = 2;i <= n;++i){for(int j = 0;j <= m;++j){for(int k = 0;k+j <= m;++k){//0dp[i][j][k] = dp[i-1][j][k];//1if(j >= 1){ll p = m - k - (j-1);dp[i][j][k] += dp[i-1][j-1][k]*p%mod;dp[i][j][k] %= mod;}if(k-1 >= 0){dp[i][j][k] += dp[i-1][j+1][k-1]*(j+1)%mod;dp[i][j][k] %= mod;}//2if(j >= 2){ll p = (m-k-j+2);p = p*(p-1)/2%mod;dp[i][j][k] += dp[i-1][j-2][k]*p%mod;dp[i][j][k] %= mod;}if(k >= 2){ll p = (j+2)*(j+1)/2%mod;dp[i][j][k] += dp[i-1][j+2][k-2]*p%mod;dp[i][j][k] %= mod;}if(k >= 1){ll p = m - j - (k-1);dp[i][j][k] += dp[i-1][j][k-1]*j*p%mod;dp[i][j][k] %= mod;}//dp[i][j][k] += }}}ll ans = 0;for(int i = 0;i <= m;++i){for(int j = 0;j+i <= m;++j){ans = (ans + dp[n][i][j])%mod;}}cout<<ans<<endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321255.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【DP】【高精】幸运票 (jzoj 2122)

幸运票 题目大意&#xff1a; 一个长度为2N的序列&#xff0c;这些数的总和为S&#xff0c;当这个序列的前N个和后N个总和相等时&#xff0c;它是符合题意的&#xff0c;问有符合题意的有多少种可能 样例输入 2 2 样例输出 4 数据范围限制 1<N<50 S<1000 解…

51nod-有限背包计数问题【dp】

正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId1597 题目大意 nnn种物品&#xff0c;第iii个大小为iii且有iii个。 求恰好填满大小为nnn的背包的方案数 解题思路 我们可以将背包分为两份&#xff0c;对于大小小于等于n\sqrt nn​的物品&#xff0c;这…

Build 2018大会:.NET概述和路线图

在Microsoft Build 2018大会上&#xff0c;.NET项目管理主管Scott Hunter和.NET社区主管Scott Hanselman举行了一场有关.NET未来发展的会谈。会谈指出&#xff0c;未来.NET平台将可以搭建任何类型的应用程序&#xff1a;桌面程序、web程序、云程序、移动应用、游戏应用、物联网…

洛谷P2278操作系统 模拟+堆

一道模拟题 竟然活生生的不会模拟&#xff0c;感觉自己好菜啊。 在模拟的时候&#xff0c;一定要弄清楚要对什么进行模拟。 题解 进程的等待队列是一个优先队列&#xff0c;优先队列是以优先级降序作为第一关键字&#xff0c;以进入时间为第二关键字。在操作系统这道题目中…

【二分】抄书 (jzoj 2123)

抄书 题目大意&#xff1a; 有n本书&#xff0c;分给m个人抄&#xff0c;每个人只能拿到连续的书&#xff08;不能把一本书分开&#xff09;&#xff0c;问抄书最多的人要抄多少页 样例输入 9 3 100 200 300 400 500 600 700 800 900 样例输出 1700 数据范围限制 对于…

nssl1467-U【差分】

正题 题目大意 n∗nn*nn∗n的矩阵&#xff0c;每次让一个下三角形内数字加上一定权值。求最后所有位置的异或和 解题思路 我们发现如果我们对于没行做前缀和的话&#xff0c;我们需要修改的位置就是一个竖直下去的一列和斜着的一条&#xff0c;所以我们可以分别对于竖着的和斜…

汽车之家汽车品牌Logo信息抓取 DotnetSpider实战[三]

一、正题前的唠叨第一篇实战博客&#xff0c;阅读量1000&#xff0c;第二篇&#xff0c;阅读量200&#xff0c;两篇文章相差近5倍&#xff0c;这个差异真的令我很费劲&#xff0c;截止今天&#xff0c;我一直在思考为什么会有这么大的差距&#xff0c;是因为干货变少了&#xf…

洛谷P1801 黑匣子 双堆套路的使用

题意 题目链接 题解 这道题本可以用Treap暴力求解出来&#xff0c;但是不够优雅&#xff0c;因为没有充分利用到题目中给的条件&#xff0c;那就是要求的ithith小的值的ii是单调递增的。我们用两个堆来维护,大顶堆和小顶堆。 大顶堆中的元素是排好序的前i&#x2212;1&qu…

2019.01.26【NOIP普及组】模拟赛C组总结

总结 这次比赛的得分是&#xff1a;10001060170 第一题想了一会&#xff0c;想到了方法&#xff0c;直接打出来&#xff0c;第二题不会&#xff0c;想水分&#xff0c;但没水到&#xff0c;第三题打了一个假的DP&#xff0c;10分&#xff0c;第四题用DP超时了&#xff0c;60分…

nssl1468-V【状压,数学期望,dfs】

正题 题目大意 nnn个球排成一排颜色不同&#xff0c;每次选择一个随机的[1..n][1..n][1..n]中的xxx&#xff0c;然后删掉第xxx个或第n−x1n-x1n−x1个数&#xff0c;求删kkk次之后删掉的白球最多&#xff0c;求删掉数量的期望值 解题思路 考虑状态压缩dpdpdp&#xff0c;定义第…

洛谷 一种堆套路 P1631序列合并、P2085最小函数值

题目链接 序列合并 最小函数值 题解 这两道题做法基本一样&#xff0c;是使用同一种套路解决的&#xff0c;这里用序列合并来举例说明。 序列合并要求出N2N2个和中最小的N个数。 我们用一个堆来维护我们需要的数&#xff0c;并且保证当前最小值一定在堆中。 把a和b排个序…

.NET Core 2.1 正式发布

这次更新包括对性能的改进&#xff0c;对运行时和工具的改进。还包含一种以 NuGet 包的形式部署工具的新方法。我们添加了一个名为 Span<T> 的新基元类型&#xff0c;它可以在没有内存分配的情况下对数据进行操作。还有许多其他新的 API&#xff0c;专注于密码学&#xf…

纪中培训总结(2019年1月21~31日)

Day 0&#xff08;21号&#xff09; 中午从家里出发&#xff0c;坐了两个小时的车&#xff08;堵得要命&#xff09;&#xff0c;过了虎门大桥&#xff0c;在一个服务站吃起了晚餐&#xff08;麦当劳的包&#xff09;&#xff0c;又坐了一个小时的车&#xff0c;终于到了&…

nssl1469-W【dp】

正题 题目大意 nnn个点的一棵树&#xff0c;每条边一个权值为0或1和一个目标权值&#xff08;0或1或者没有限制&#xff09;。每次可以将一个路径上的权值取反&#xff0c;求最小翻转数量和最小翻转路径长度。 解题思路 首先我们可以从序列的类似问题上知道一条边不会被翻转超…

[翻译] 比较 Node.js,Python,Java,C# 和 Go 的 AWS Lambda 性能

原文: Comparing AWS Lambda performance of Node.js, Python, Java, C# and GoAWS 最近宣布他们支持了 C&#xff03; (Net Core 2.0 版本) 和 Go 语言来实现 Lambda 功能。(译者注: AWS Lambda 是 AWS 推出的 Serverless 功能&#xff0c;请参阅这里或 Serverless 相关资料)做…

codeforces gym-101745 C-Infinite Graph Game 分块

题意 题目链接 给出一个顶点带权无向图。 定义访问操作&#xff1a;访问一个点&#xff0c;就要把与这个点相邻的点的权值全部都加到答案里去&#xff0c;然后给这个顶点的权值/2。现在给出一个无穷的访问序列中的一个循环节&#xff0c;求最终答案的极限是多少。 注意&…

P5579-[PA2015]Siano【线段树】

正题 题目链接:https://www.luogu.com.cn/problem/P5579 题目大意 nnn个树&#xff0c;第iii个每天长高aia_iai​米。 mmm次修剪&#xff0c;第iii次在did_idi​天&#xff0c;将高度为bib_ibi​的部分修剪掉 求每次修剪掉的高度 解题思路 按照aia_iai​排序后我们知道每次修…

【结论】立体井字棋(jzoj 2124)

立体井字棋 题目大意&#xff1a; 在一个nnn的正方体中&#xff0c;由n个格子连成一条直线的方案数&#xff08;多少种可能用n个格子连成一条直线&#xff09; 样例输入 2 样例输出 28 数据范围限制 对于30%的数据&#xff0c; n<10&#xff1b; 对于100%的数据&am…

ASP.NET Core Identity 实战(3)认证过程

如果你没接触过旧版Asp.Net Mvc中的 Authorize 或者 Cookie登陆&#xff0c;那么你一定会疑惑 认证这个名词&#xff0c;这太正式了&#xff0c;这到底代表这什么&#xff1f;获取资源之前得先过两道关卡Authentication & Authorization要想了解Identity中用户登录之后&…

codeforces gym-101745 D-Stamp Stamp Stamp动态规划

题解 一道很不错的动态规划问题&#xff0c;首先这些印章一定是s的子串。 我们可以枚举s的子串然后进行check。 如何check&#xff0c;成了这道题的关键。 由于盖章的顺序不知道&#xff0c;所以我们可以使用动态规划的方法。 我们定义状态&#xff1a; dp[i][j]dp[i][j]…