UVa 10599

题意:

  给出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,记忆化搜索】的更多相关文章

  1. uva 10891 区间dp+记忆化搜索

    https://vjudge.net/problem/UVA-10891 给定一个序列x,A和B依次取数,规则是每次只能从头或者尾部取走若干个数,A和B采取的策略使得自己取出的数尽量和最大,A是先手, ...

  2. 状压DP+记忆化搜索 UVA 1252 Twenty Questions

    题目传送门 /* 题意:给出一系列的01字符串,问最少要问几个问题(列)能把它们区分出来 状态DP+记忆化搜索:dp[s1][s2]表示问题集合为s1.答案对错集合为s2时,还要问几次才能区分出来 若 ...

  3. UVA - 10118Free Candies(记忆化搜索)

    题目:UVA - 10118Free Candies(记忆化搜索) 题目大意:给你四堆糖果,每一个糖果都有颜色.每次你都仅仅能拿随意一堆最上面的糖果,放到自己的篮子里.假设有两个糖果颜色同样的话,就行 ...

  4. 【bzoj5123】[Lydsy12月赛]线段树的匹配 树形dp+记忆化搜索

    题目描述 求一棵 $[1,n]$ 的线段树的最大匹配数目与方案数. $n\le 10^{18}$ 题解 树形dp+记忆化搜索 设 $f[l][r]$ 表示根节点为 $[l,r]$ 的线段树,匹配选择根 ...

  5. 【BZOJ】1415 [Noi2005]聪聪和可可 期望DP+记忆化搜索

    [题意]给定无向图,聪聪和可可各自位于一点,可可每单位时间随机向周围走一步或停留,聪聪每单位时间追两步(先走),问追到可可的期望时间.n<=1000. [算法]期望DP+记忆化搜索 [题解]首先 ...

  6. [题解](树形dp/记忆化搜索)luogu_P1040_加分二叉树

    树形dp/记忆化搜索 首先可以看出树形dp,因为第一个问题并不需要知道子树的样子, 然而第二个输出前序遍历,必须知道每个子树的根节点,需要在树形dp过程中记录,递归输出 那么如何求最大加分树——根据中 ...

  7. poj1664 dp记忆化搜索

    http://poj.org/problem?id=1664 Description 把M个相同的苹果放在N个相同的盘子里,同意有的盘子空着不放,问共同拥有多少种不同的分法?(用K表示)5.1.1和1 ...

  8. 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. ...

  9. POJ 1088 DP=记忆化搜索

    话说DP=记忆化搜索这句话真不是虚的. 面对这道题目,题意很简单,但是DP的时候,方向分为四个,这个时候用递推就好难写了,你很难得到当前状态的前一个真实状态,这个时候记忆化搜索就派上用场啦! 通过对四 ...

随机推荐

  1. Leetcode80. Remove Duplicates from Sorted Array II删除排序数组中的重复项2

    给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. 示例  ...

  2. GUID(Globally Unique Identifier)全局唯一标识符

    最近有大量数据存入数据库时,因为主键为一个nvarchar类型,起初想着用int 类型,每次打开表的时候,获取最后一行的ID,然后让其++. 但发现由于字段是char类型,数据库对其进行了排序.再次插 ...

  3. 工控安全入门(二)—— S7comm协议

    在上一次的文章中我们介绍了施耐德公司的协议modbus,这次我们把目标转向私有协议,来看看另一家巨头西门子的S7comm.首先要说明,这篇文章中的内容有笔者自己的探索,有大佬们的成果,但由于S7com ...

  4. python实例 列表

    #! /usr/bin/python # -*- coding: utf8 -*- #列表类似Javascript的数组,方便易用 #定义元组 word=['a','b','c','d','e','f ...

  5. deque简单解析

    deque是支持双端插入删除的容器,oi中用来维护单调队列 声明方式 deque<int> d1;//声明一个叫d1的双向队列 deque<int> d2(d1);//声明一个 ...

  6. 对C语言内存对齐的初步了解

    在解释内存对齐的作用前,先来看下内存对齐的规则: 1. 对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身 ...

  7. JavaScript之HTML DOM Document 对象

    文档对象代表您的网页. 如果您希望访问 HTML 页面中的任何元素,那么您总是从访问 document 对象开始. 下面是一些如何使用 document 对象来访问和操作 HTML 的实例. 查找 H ...

  8. KMLLayer

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. C#和JS交互 WebBrowser实例

    本文实现了WebBrowser的简单例子 1.引用System.Windows.Froms.dll 2.引用WindowsFormsIntegration.dll 代码如下: public parti ...

  10. Linux SSH远程链接 短时间内断开

    Linux SSH远程链接 短时间内断开 操作系统:RedHat 7.5 问题描述: 在进行SSH链接后,时不时的就断开了 解决方案: 修改 /etc/ssh/sshd_config 文件,找到 Cl ...