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. vim编辑器操作①

    Linux文本编辑器: 行编辑器:sed 全屏编辑器:nano,vi/vim 本文主要介绍说明vim编辑器的相关使用: 其有三种模式,即: 编辑模式(默认模式).插入模式(输入模式).末行模式(内置的 ...

  2. Redis源码解析:23sentinel(四)故障转移流程

    十:故障转移流程中的状态转换 当哨兵针对某个主节点进行故障转移时,该主节点的故障转移状态master->failover_state,要依次经历下面六个状态: SENTINEL_FAILOVER ...

  3. [Array]485. Max Consecutive Ones

    Given a binary array, find the maximum number of consecutive 1s in this array. Example 1: Input: [1, ...

  4. Thinkphp [美味]常用代码

    //调试开关 function _initialize () { // 调试开关 C ( 'SHOW_PAGE_TRACE', TRUE ); } //判断 IS_AJAX && $t ...

  5. linux 下环境变量设置

    Ubuntu Linux系统包含两类环境变量:系统环境变量和用户环境变量.系统环境变量对所有系统用户都有效,用户环境变量仅仅对当前的用户有效. 修改用户环境变量 用户环境变量通常被存储在下面的文件中: ...

  6. Ubuntu 16.04 LTS安装Docker最新版

    一.安装Docker的先决条件 1.运行64位CPU构架的计算机(目前只能是x86_64和amd64),请注意,Docker目前不支持32位CPU.2.运行Linux 3.8或更高版本内核.一些老版本 ...

  7. 字符串无法分割 split无效: java split()使用“.” “\” "|" "*" "+"要转义

    .是特殊字符 特殊字符需要转义. 改成split(“\\.”)

  8. Hibernate4的注解 (持续更新范例中)

    作用:使得Hibernate程序的开发大大的简化.利用注解后,可不用定义持久化类对应的*.hbm.xml,而直接以注解方式写入持久化类的实现中. 注解配置持久化类常用注解. 注解 含义和作用 @Ent ...

  9. idea使用docker插件

    idea使用docker插件 接着上一篇docker开启远程访问后,我们就可以通过idea使用docker插件把项目部署到docker了. 首先我们先在idea安装docker插件: 在setting ...

  10. 关系数据库标准语言 SQL (ch.3)

    3.1 SQL 概述 3.1.2 特点 1 综合统一 非关系型语言 的数据语言都分为 DDL Scheme Data Definitin Language, 模式DDL SubScheme Data ...