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

05-11 14:45