UVa 10599【lis dp,记忆化搜索】
题意:
给出r*c的网格,其中有些格子里面有垃圾,机器人从左上角移动到右下角,只能向右或向下移动。问机器人能清扫最多多少个含有垃圾的格子,有多少中方案,输出其中一种方案的格子编号。格子编号是从 左上角第一个开始,一行一行的按自然数顺序编。起始行列是第一行第一列。所以例如一个格子的行列号为(ro,co),那么它的编号为bh=(ro-1)*column+co,其中column指这个格子有多少列。(我觉得原题里面有个错误,题目叙述倒数第二行应该是41(6,6)不是41(6,7))。
题解:
显然,格子的编号都是递增的,每个含有垃圾的格子的编号也是递增的,要求能扫多少个有垃圾的格子,其实可以看成求这些垃圾格子编号的一个最长上升子序列(lis),而且这和普通格子没关系。需要注意的就是求lis时格子编号大的一定不能在编号小的左边,可以在同一列上。因为机器人只能向下或向右走。所以判断的时候要判断一下两个格子的列大小,这个也可以转化为比较编号的大小:(bh-1)%colum,可以算一下这个式子正好算出格子的 列号-1。当然,这里可以用其它办法判断。然后就是用dp[]数组记录最多清扫多少个格子,用save[]记录垃圾格子的编号,用num[]数组记录方案总数(就是看成普通lis求法),用patn[]数组记录当前状态是从哪里转移过来的,也就是记录路径。
详见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = ;
int Map[maxn][maxn];//存那个格子有垃圾
int dp[maxn*maxn], num[maxn*maxn], path[maxn*maxn], save[maxn*maxn];//如上文所述
int r, c, n;//格子行、列,有多少个垃圾格子 void print(int cur)//递归输出
{
if (path[cur] != -)
print(path[cur]);
if (cur != n - || Map[r][c])//因为从n-1开始调用,当右下角格子不是垃圾格子时不需要输出(cur!=n-1),是垃圾格子时需要输出(Map[r][c])
printf(" %d", save[cur]);
} int main()
{
int cas = ;
while (cin >> r >> c)
{
memset(Map, , sizeof(Map));
memset(save, , sizeof(save));
if (r == - && c == -) break;
int a, b;
while (cin >> a >> b)
{
if (a == && b == ) break;
Map[a][b] = ;
}
n = ;
for (int i = ; i <= r; i++)
for (int j = ; j <= c; j++) {
if (Map[i][j])
save[n++] = (i - )*c + j;//为垃圾格子编号
}
if (!Map[r][c]) save[n++] = r*c;//因为最后要到达右下角,所以不管右下角是不是垃圾格子,都把它看成有,求"lis"过程好办点
for (int i = ; i <= n; i++) {//dp过程,和求lis过程类似
dp[i] = ; num[i] = ; path[i] = -;
for (int j = ; j < i; j++) {
if (((save[j] - ) % c) <= ((save[i] - ) % c)) {//比较列
if (dp[i] == dp[j] + ) {//此时相当于又多了一种到i状态(第i个数)的方案数,那么直接累加num[j]
num[i] += num[j];
}
else if (dp[i] < dp[j] + ) {//此时状态可更新
dp[i] = dp[j] + ;//更新状态
num[i] = num[j];//改为新状态的方案
path[i] = j;//由于有新的状态,所以记录到当前状态的上一个状态位置
}
}
}
}
if (!Map[r][c]) dp[n - ]--;//当右下角那个不是垃圾格子时,能清理的垃圾格子数-1
printf("CASE#%d: %d %d", cas++, dp[n - ], num[n - ]);
print(n - );//输出路径
printf("\n");
}
return ;
}
UVa 10599 code_1
网上见到还有人用记忆化搜索来写,其实我不太懂什么叫记忆化搜素。我的理解就是在搜索的时候保存搜过的状态。dp也是保存状态,自底向上推的时候就是由小问题向大问题求解并保存状态,自顶向下就是由回溯求解过程中保存状态,觉得这样就是记忆化搜索>_<.。感觉自己对递归,回溯,深搜理解的还是不够深刻。只能写一些简单的递归、深搜,只理解到递归会层层往上返回答案。但看别人写的时候去很难想到为什么这样写,他们好像把dfs抽象了一下,到某个状态要是搜了就不管了,直接返回答案。这样中间其实很多步骤都不用完全知道,只要保证自己写的正确就行了,反观我写dfs时绝大部分时候都要知道所有的状态情况,这样写起来很累,而且容易出错,调试也麻烦。感觉自己这方面需要加强!
说回这道题,先求能扫多少个垃圾格子,其实刚开始我也是dfs,就从dfs(1,1)开始搜呗,到(row,col)结束,但我写的总有问题。我觉得问题在没有抽象的理解递归和回溯,只知道它会一层一层往回返回答案,但其实这个返回的顺序是不一定按照自己思路里的那个顺序的而且一个位置可能重复搜好多次。所以只有抽象的理解,设定好结束状态和保存搜过的状态,按照要求写下去才会不易出错。这道题dfs求的就是最大清扫垃圾格子的数目,保存在f[1][1]。我以前的想法是走一步加一步,存最后结果的应该是f[row][col]。但这个却是存在[f1][1],利用每一层返回的值,相当于倒着往回求出结果,需要我深刻体会啊!后悔solve()求最优路径个数其实道理也差不多,num[]数组存某个点到终点的路径个数,最后也是返回结果num[1][1],也是倒着推回来。设定结束状态,到[row][col]就返回,再记忆化存结果返回,即存过的状态(num[i][j]!=-1)不用搜了。后面就是从当前点开始,满足要求的点(g[i][j]&&g[i][j]+f[i][j]==f[r][c])就再搜,并累加答案。最后路径输出也是类似的递归,判断输出的最后一个点,然后return,其他时候输出完再继续递归。
最后,我又一次感觉到了代码好美。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = ;
int f[N][N], g[N][N], num[N][N];
int row, col, res; int dfs(int r, int c)
{
if (f[r][c] != -) return f[r][c];
if (r == row&&c == col) return f[r][c] = g[r][c];
f[r][c] = ;
if (r + <= row)
f[r][c] = max(f[r][c], dfs(r + , c) + g[r][c]);
if (c + <= col)
f[r][c] = max(f[r][c], dfs(r, c + ) + g[r][c]);
return f[r][c];
} int solve(int r, int c)
{
if (num[r][c] != -) return num[r][c];
if (f[r][c] == ) return num[r][c] = f[r][c];
num[r][c] = ;
for (int i = r; i <= row; i++)
for (int j = c; j <= col; j++)
if (g[i][j] && g[i][j] + f[i][j] == f[r][c])
num[r][c] += solve(i, j);
return num[r][c];
} void print_path(int r, int c)
{
if (g[r][c] && f[r][c] == ) {
printf(" %d\n", (r - )*col + c);
return;
}
printf(" %d", (r - )*col + c);
for(int i=r;i<=row;i++)
for(int j=c;j<=col;j++)
if (g[i][j] && g[i][j] + f[i][j] == f[r][c]) {
print_path(i, j);
return;
}
} void print()
{
for(int i=;i<=row;i++)
for(int j=;j<=col;j++)
if (g[i][j] && f[i][j] == res) {
print_path(i, j);
return;
}
} int main()
{
int cas = , a, b;
while (cin>>row>>col)
{
if (row == - && col == -) break;
memset(g, , sizeof(g));
memset(f, -, sizeof(f));
memset(num, -, sizeof(num));
while (cin>>a>>b)
{
if (a == && b == ) break;
g[a][b] = ;
}
res = dfs(, );
solve(, );
printf("Case#%d: %d %d", cas++, res, num[][]);
print();
}
return ;
}
UVa 10599 code_2
UVa 10599【lis dp,记忆化搜索】的更多相关文章
- uva 10891 区间dp+记忆化搜索
https://vjudge.net/problem/UVA-10891 给定一个序列x,A和B依次取数,规则是每次只能从头或者尾部取走若干个数,A和B采取的策略使得自己取出的数尽量和最大,A是先手, ...
- 状压DP+记忆化搜索 UVA 1252 Twenty Questions
题目传送门 /* 题意:给出一系列的01字符串,问最少要问几个问题(列)能把它们区分出来 状态DP+记忆化搜索:dp[s1][s2]表示问题集合为s1.答案对错集合为s2时,还要问几次才能区分出来 若 ...
- UVA - 10118Free Candies(记忆化搜索)
题目:UVA - 10118Free Candies(记忆化搜索) 题目大意:给你四堆糖果,每一个糖果都有颜色.每次你都仅仅能拿随意一堆最上面的糖果,放到自己的篮子里.假设有两个糖果颜色同样的话,就行 ...
- 【bzoj5123】[Lydsy12月赛]线段树的匹配 树形dp+记忆化搜索
题目描述 求一棵 $[1,n]$ 的线段树的最大匹配数目与方案数. $n\le 10^{18}$ 题解 树形dp+记忆化搜索 设 $f[l][r]$ 表示根节点为 $[l,r]$ 的线段树,匹配选择根 ...
- 【BZOJ】1415 [Noi2005]聪聪和可可 期望DP+记忆化搜索
[题意]给定无向图,聪聪和可可各自位于一点,可可每单位时间随机向周围走一步或停留,聪聪每单位时间追两步(先走),问追到可可的期望时间.n<=1000. [算法]期望DP+记忆化搜索 [题解]首先 ...
- [题解](树形dp/记忆化搜索)luogu_P1040_加分二叉树
树形dp/记忆化搜索 首先可以看出树形dp,因为第一个问题并不需要知道子树的样子, 然而第二个输出前序遍历,必须知道每个子树的根节点,需要在树形dp过程中记录,递归输出 那么如何求最大加分树——根据中 ...
- poj1664 dp记忆化搜索
http://poj.org/problem?id=1664 Description 把M个相同的苹果放在N个相同的盘子里,同意有的盘子空着不放,问共同拥有多少种不同的分法?(用K表示)5.1.1和1 ...
- ACM International Collegiate Programming Contest, Tishreen Collegiate Programming Contest (2017)- K. Poor Ramzi -dp+记忆化搜索
ACM International Collegiate Programming Contest, Tishreen Collegiate Programming Contest (2017)- K. ...
- POJ 1088 DP=记忆化搜索
话说DP=记忆化搜索这句话真不是虚的. 面对这道题目,题意很简单,但是DP的时候,方向分为四个,这个时候用递推就好难写了,你很难得到当前状态的前一个真实状态,这个时候记忆化搜索就派上用场啦! 通过对四 ...
随机推荐
- (2)卷积 & 卷积和
参考资料:<信号与系统(第二版)> 杨晓非 何丰 连续信号的是卷积积分,离散信号的是卷积和. 脉冲分量 任意非周期信号,将横坐标分为若干个微小等分,得到Δτ为宽,f(kΔτ)为高的一系列微 ...
- java反编译工具使用记录
最近试了四个反编译工具,总结一下. 先说结论,最有效果的是Procyon Decompile.使用方法:https://blog.csdn.net/u010762551/article/details ...
- ESB介绍
通过使用ESB,可以在几乎不更改代码的情况下,以一种无缝的非侵入方式使企业已有的系统具有全新的服务接口,并能够在部署环境中支持任何标准.更重要的是,充当“缓冲器”的ESB(负责在诸多服务之间转换业务逻 ...
- Thinkphp 数据库配置参数
mysql配置 'DB_USER' => array( 'DB_TYPE' => 'mysql', 'DB_HOST' => '127.0.0.1', 'DB_NAME' => ...
- 项目中的那些事---Java反射的应用
最近工作中遇到一个这样的问题: 为某个项目中的所有接口做一个测试工具,使用java Swing技术,该项目有不同的版本,不是所有版本中的接口都是相同的,而我做的工具需要兼容所有版本. 于是就引入了这样 ...
- JavaScript-JQ初探实现自定义滚动条
这是一个基本实现思路,如果有新手和我一样没什么事,喜欢瞎研究话,可以参考下. 一.Html <div class="scroll_con"> <div class ...
- Spring 社区的首个国产开源项目顺利毕业
相信大家对上周的 <来自 Spring Cloud 官方的消息,Spring Cloud Alibaba 即将毕业>文章记忆犹新.本周,Spring Cloud Alibaba 正式毕业, ...
- LUOGU P2921 [USACO08DEC]在农场万圣节Trick or Treat on the Farm
传送门 解题思路 记忆化搜索,如果搜到环,就将环的大小处理出来. 代码 #include<iostream> #include<cstdio> #include<cstr ...
- 由一道面试题引起的arguments的思考
写一个按照下面方式调用都能正常工作的 sum 方法 console.log(sum(2,3)); // Outputs 5 console.log(sum(2)(3)); // Outputs 5从这 ...
- Div+CSS常见错误总结
CSS+DIV是网站标准(或称“WEB标准”)中常用的术语之一,通常为了说明与HTML网页设计语言中的表格(table)定位方式的区别,因为XHTML网站设计标准中,不再使用表格定位技术,而是采用cs ...