Round C APAC Test 2017 Problem A. Monster Path (C++)

Problem

Codejamon is a mobile game in which monster trainers walk around in the real world to catch monsters. You have an old smartphone with a short battery life, so you need to choose your path carefully to catch as many monsters as possible.

Suppose the Codejamon world is a rectangular grid with R rows and C columns. Rows are numbered from top to bottom, starting from 0; columns are numbered from left to right, starting from 0. You start in the cell in the Rsth row and the Csth column. You will take a total of S unit steps; each step must be to a cell sharing an edge (not just a corner) with your current cell.

Whenever you take a step into a cell in which you have not already caught a monster, you will catch the monster in that cell with probability P if the cell has a monster attractor, or Q otherwise. If you do catch the monster in a cell, it goes away, and you cannot catch any more monsters in that cell, even on future visits. If you do not catch the monster in a cell, you may still try to catch the monster on a future visit to that cell. The starting cell is special: you have no chance of catching a monster there before taking your first step.

If you plan your path optimally before making any move, what is the maximum possible expected number of monsters that you will be able to catch?

The battery can only support limited steps, so hurry up!
Input

The first line of the input gives the number of test cases, T. T test cases follow.

Each test case starts with a line of five integers: R, C, Rs, Cs and S. R and C are the numbers of rows and columns in the grid; Rs and Cs
are the numbers of the row and column of your starting position, and S is the number of steps you are allowed to take.

The next line contains two decimals P and Q, where P is the probability of meeting a monster in cells with a monster attractor, and Q is the probability of meeting a monster in cells without a monster attractor. P and Q are each given to exactly four decimal places.

Each of the next R lines contains contains C space-separated characters; the j-th character of the i-th line represents the cell at row i and column j. Each element is either . (meaning there is no attractor in that cell) or A (meaning there is an attractor in that cell).

Output

For each test case, output one line containing Case #x: y, where x is the test case number (starting from 1) and y is the largest possible expected number of monsters that the player can catch in the given amount of steps.

y will be considered correct if y is within an absolute or relative error of 10-6 of the correct answer. See the FAQ for an explanation of what that means, and what formats of real numbers we accept.

Limits

1 ≤ T ≤ 100.
0 ≤ Rs < R.
0 ≤ Cs < C.
0 ≤ Q < P ≤ 1.
Small dataset

1 ≤ R ≤ 10.
1 ≤ C ≤ 10.
0 ≤ S ≤ 5.
Large dataset

1 ≤ R ≤ 20.
1 ≤ C ≤ 20.
0 ≤ S ≤ 9.

分析:

  • 广度优先算法。
  • 递归进行,每步记录:当前剩余步数step、当前期望值cur、当前位置(i, j)、当前访问过的网格状态visited。
  • 一旦剩余步数为step==0,则将cur与全局result相比,取大值。
  • 每一步向上、下、左、右4个方向递归(注意越界),并在cur上加上相应可能性,调整当前位置,更新网格状态visited,即在当前位置上+1,说明该位置访问过过visited[i][j]次(计算要加上的相应可能性时需要用)。
  • 计算每步cur的增量:假设该位置之前访问过x次。若有attractor,则当前增量=[(1-P)^(x)]*P, 其中[(1-P)^(x)]表示之前x次没抓到monster的概率。若无attractor,则相应增量=[(1-Q)^x]*Q

代码:Github

#include <iostream>
#include <cmath>
#include <vector>
#include <stack>
#include <queue>
#include <algorithm>
#include <fstream>
#include <iomanip>

using namespace std;

typedef long long ll;

int T;

int R, C, Rs, Cs, S;
double p;
double q;
double result = 0;

void solve(double cur, int i, int j, int step, vector<vector<char>>game, vector<vector<int>> visited) {
    if (step == 0) {
        result = max(result, cur);
        return;
    }
    if (i - 1 >= 0) {
        visited[i - 1][j]++;
        if (game[i - 1][j] == '.') solve(cur + pow((1 - q), (double)(visited[i - 1][j]) - 1)*q, i - 1, j, step - 1, game, visited);
        else solve(cur + pow((1 - p), (double)(visited[i - 1][j]) - 1)*p, i - 1, j, step - 1, game, visited);
        visited[i - 1][j]--;
    }
    if (j - 1 >= 0) {
        visited[i][j - 1]++;
        if (game[i][j - 1] == '.') solve(cur + pow((1 - q), (double)(visited[i][j - 1]) - 1)*q, i, j - 1, step - 1, game, visited);
        else solve(cur + pow((1 - p), (double)(visited[i][j - 1]) - 1)*p, i, j - 1, step - 1, game, visited);
        visited[i][j - 1]++;
    }

    if (i + 1 < R) {
        visited[i + 1][j]++;
        if (game[i + 1][j] == '.') solve(cur + pow((1 - q), (double)(visited[i + 1][j]) - 1)*q, i + 1, j, step - 1, game, visited);
        else solve(cur + pow((1 - p), (double)(visited[i + 1][j]) - 1)*p, i + 1, j, step - 1, game, visited);
        visited[i + 1][j]--;
    }

    if (j + 1 <C) {
        visited[i][j + 1]++;
        if (game[i][j + 1] == '.') solve(cur + pow((1 - q), (double)(visited[i][j + 1]) - 1)*q, i, j + 1, step - 1, game, visited);
        else solve(cur + pow((1 - p), (double)(visited[i][j + 1]) - 1)*p, i, j + 1, step - 1, game, visited);
        visited[i][j + 1]++;
    }
}


int main() {
    ifstream f_input;
    ofstream f_output;

    f_input.open("A-large-practice.in");
    f_output.open("out_put");

    f_input >> T;
    for (int tt = 1; tt <= T; tt++) {
        result = 0;
        f_input >> R >> C >> Rs >> Cs >> S;
        f_input >> p >> q;
        vector<vector<char>> game(R, vector<char>(C, '.'));
        for (int i = 0; i < R; i++)
        for (int j = 0; j < C; j++)
            f_input >> game[i][j];

        vector<vector<int>> visited(R, vector<int>(C, 0));
        solve(0, Rs, Cs, S, game, visited);
        f_output << "Case #" << tt << ": ";
        f_output.setf(ios::fixed, ios::floatfield); 
        f_output << setprecision(7);
        f_output<< result << endl;
        cout << tt << "finished" << endl;
    }
    f_input.close();
    f_output.close();
}
<think> 我们正在处理Tcl代码片段:`-expr {round([lindex $obj(.path.y) 0]*1000)}` 这个代码片段的目标是从一个对象`$obj`中获取路径`.path.y`的第一个元素(索引0),然后乘以1000,最后四舍五入取整。 分析: 1. `$obj(.path.y)`:这看起来像是在访问一个数组或字典`obj`中键为`.path.y`的元素。但在Tcl中,数组访问使用`$array(index)`,而这里使用了圆括号,实际上Tcl数组使用圆括号作为下标,但是这里整个被括号包围,可能是在一个需要表达式的地方使用。 2. `[lindex ... 0]`:`lindex`用于从列表中获取指定索引的元素,这里获取第一个元素(索引0)。 3. 整个表达式:`round([lindex $obj(.path.y) 0]*1000)`。注意,这里有一个潜在的问题:`$obj(.path.y)`返回的值是一个列表吗?因为`lindex`的第一个参数应该是一个列表。如果不是列表,可能会出错。 优化建议: 1. 首先,确保`$obj(.path.y)`返回的是一个列表。如果不是,那么可能需要调整数据获取方式。 2. 如果`.path.y`对应的值是一个单一数值(比如浮点数),那么实际上我们不需要用`lindex`来取第一个元素,直接使用即可。但这里乘以1000后取整,可能是一个数值转换(例如,将米转换为毫米)。 但是,观察表达式:`[lindex $obj(.path.y) 0]`,这意味着`.path.y`对应的值应该是一个列表,而我们只取第一个元素。如果这个列表只有一个元素,那么这样写没问题。但是,如果这个列表有多个元素,而我们只需要第一个,那也是合理的。 然而,代码中有一个语法错误:在Tcl中,数组元素访问的正确形式是`$arr(index)`,但是这里在数组名`obj`和下标`.path.y`之间没有美元符号?实际上,整个`obj(.path.y)`应该被看作一个数组元素访问。但是在表达式内部,我们通常使用变量替换,所以这里写成`$obj(.path.y)`是正确的吗? 实际上,在Tcl中,数组元素的引用必须是: set value $obj(.path.y) 但是在表达式内部,不能直接使用`$obj(.path.y)`,因为表达式解析器要求数组元素必须用大括号括起来或者使用其他方式避免解析问题。在`expr`中,数组元素通常需要写成`[array get obj .path.y]`或者使用变量替换。然而,这里使用的写法在`expr`中是错误的。 因此,我们可能需要将代码改为: -expr {round([lindex $obj(.path.y) 0]*1000)} 实际上,这里`$obj(.path.y)`是在执行数组访问,但请注意,在表达式中,`$`符号用于变量替换,所以`$obj(.path.y)`会被替换为数组元素的值。然而,在Tcl中,数组元素的值可以是任何字符串,包括一个列表。 但是,这里有一个更严重的问题:在表达式内部,`.`会被解释为浮点数的一部分,或者作为操作符?实际上,在数组下标中,`.`是下标字符串的一部分,不应该被解释为操作符。然而,在表达式内部,整个数组元素访问的写法是不被允许的,因为表达式解析器不会解析数组下标。 所以,通常的做法是:在表达式外部获取数组元素的值,然后传递给表达式,或者在表达式内部使用命令替换来获取数组元素。 因此,原代码可能应该写成: -expr {round([lindex [set obj(.path.y)] 0]*1000)} 但是,这种写法也是错误的,因为`set obj(.path.y)`并不是合法的Tcl命令(`set`用于变量,而数组元素需要指定数组名和下标,例如`set arr(index)`)。 正确的写法应该是: -expr {round([lindex $obj(.path.y) 0]*1000)} 实际上,在Tcl中,数组元素在表达式中可以使用`${arrayName(index)}`的形式,以避免解析问题。所以,我们可以这样写: -expr {round([lindex ${obj(.path.y)} 0]*1000)} 但是,请注意,数组下标中不能包含括号(除非是嵌套数组,但Tcl不支持多维数组)。下标`.path.y`中的点号是允许的,但是整个下标需要用引号括起来吗?实际上,在数组定义时,下标可以是任意字符串,但是在访问时,如果下标包含特殊字符(如空格),则需要使用引号。这里下标是`.path.y`,没有空格,所以可以不用引号。 然而,在`${obj(.path.y)}`中,我们使用花括号将整个数组元素名称括起来,这样解析器会正确替换。 但是,这里还有一个问题:`$obj(.path.y)`的值是一个列表吗?如果是单个数值,那么`lindex`操作就没有必要,而且如果这个值不是列表,`lindex`会返回空字符串,导致错误。 所以,我们需要确认`$obj(.path.y)`的值是什么类型。假设它是一个数值列表(可能包含多个数值),而我们只需要第一个数值。 优化方案: 1. 如果`.path.y`的值是一个单元素的列表(或者说实际上就是一个数值),那么我们可以直接使用这个值,而不必使用`lindex`。但为了通用性,保留`lindex`取第一个元素也是合理的。 2. 如果这个值是一个标量数值,那么应该直接使用,而不应该用`lindex`。此时,代码应该修改为直接使用`${obj(.path.y)}`。 假设原始设计就是取列表的第一个元素,那么代码可以保持。但是,为了健壮性,我们可以考虑: - 如果`.path.y`的值不是列表,那么`lindex`会返回整个值(因为Tcl中,字符串可以当作单元素列表处理)。所以,即使`.path.y`的值是一个标量,`lindex $value 0`也会返回这个标量。 因此,我们可以保留`lindex`,因为它可以同时处理标量和列表(标量被视为单元素列表)。 但是,有一个潜在的性能问题:`lindex`每次都会解析列表。如果这个值已经是一个标量,那么解析一次列表会带来额外的开销(虽然很小)。在性能关键的代码中,我们可以考虑避免使用`lindex`,而直接使用标量。但是,如果数据可能是列表,那么使用`lindex`是安全的。 因此,优化后的代码可以保持原样,或者稍作修改以避免解析错误: 原始代码:`round([lindex $obj(.path.y) 0]*1000)` 但是,在表达式中,`$obj(.path.y)`会被替换为它的值,然后`lindex`操作这个值。然而,如果`.path.y`的值是一个包含空格的字符串(不是列表),那么`lindex`可能会分割这个字符串,取第一个单词。这显然不是我们想要的。 所以,关键问题在于:`.path.y`这个数组元素存储的数据类型是什么?如果是我们期望的数值列表,那么没有问题。但如果是其他类型,就可能出错。 建议: 1. 明确数据结构:确保`obj(.path.y)`存储的是一个列表,且第一个元素是数值类型。 2. 如果数据结构无法改变,那么我们可以增加一个判断:使用`llength`判断列表长度,如果大于0才取第一个元素,否则给一个默认值。 例如: -expr {round([lindex [list ${obj(.path.y)}] 0]*1000)} 这样,即使`${obj(.path.y)}`是一个标量,我们通过`list`命令将其转换为只有一个元素的列表。但是,如果它本身就是一个列表,那么`list`命令会将其变成嵌套列表(比如原来有两个元素,那么就会变成包含两个元素的列表作为第一个元素)。所以不行。 另一种方法:使用`split`和`join`?不,这样更不可靠。 所以,最可靠的方法是确保数据存储为列表。 另外,代码还可能存在一个问题:在表达式中,`$obj(.path.y)`的替换可能因为下标中的点号而导致解析问题?为了解决这个问题,我们使用花括号将整个数组元素引用括起来,即`${obj(.path.y)}`。 因此,优化后的代码为: -expr {round([lindex ${obj(.path.y)} 0] * 1000)} 但是,如果`.path.y`的值是一个空列表,那么`lindex`会返回空字符串,这样乘以1000就会出错。我们可以增加一个默认值,比如: -expr {round([lindex ${obj(.path.y)} 0] * 1000) ? : 0} 但是这样写比较复杂,而且Tcl的expr条件表达式是`? :`,但这里如果为空,我们可以用`if`函数(但expr中没有if函数)。所以,我们可以这样: -expr {[llength ${obj(.path.y)}] ? round([lindex ${obj(.path.y)} 0]*1000) : 0} 但这样写可读性较差。如果允许,最好在外部处理。 考虑到代码的用途,可能是用于图形坐标转换之类的,所以假设`.path.y`总是有第一个元素。因此,我们可以不加判断。 总结: 1. 使用`${obj(.path.y)}`来避免数组元素替换时的解析问题。 2. 确保`.path.y`的值是一个至少有一个元素的列表(或标量)。 因此,优化后的代码为: -expr {round([lindex ${obj(.path.y)} 0] * 1000)} 但是,如果`.path.y`的值不是列表,而是直接是一个数值,那么我们可以直接使用: -expr {round(${obj(.path.y)} * 1000)} 那么,如何选择?这取决于数据的存储方式。如果我们不能改变数据,那么我们需要同时支持两种情况。可以使用一个条件判断: set value ${obj(.path.y)} if {[llength $value] > 0} { set firstElement [lindex $value 0] } else { set firstElement 0 } -expr {round($firstElement * 1000)} 但是,在`-expr`选项中,我们只能写一个表达式,所以不能写条件语句。因此,我们可以在表达式中使用三元运算符: -expr {[llength $value] ? round([lindex $value 0]*1000) : 0} 但是,我们无法在同一个表达式中定义`value`变量。所以,我们可以这样: -expr {[llength ${obj(.path.y)}] ? round([lindex ${obj(.path.y)} 0]*1000) : 0} 然而,这样写比较长,而且每次使用都要重复三次数组访问,效率较低。 因此,如果性能要求高,最好在外部处理。但是,如果这个表达式是传递给某个选项(如Tk中的画布项),那么只能写成一个表达式。 综合考虑,如果我们确定数组元素`.path.y`的值总是至少有一个元素的列表,那么使用: -expr {round([lindex ${obj(.path.y)} 0] * 1000)} 如果可能为空,则使用: -expr {[llength ${obj(.path.y)}] ? round([lindex ${obj(.path.y)} 0]*1000) : 0} 根据实际情况选择。 另一种方案:使用`catch`处理,但在`expr`中无法使用`catch`。 所以,我建议: 如果上下文确定`.path.y`的值是一个数值(标量)或者一个非空列表,那么使用: -expr {round([lindex [list ${obj(.path.y)}] 0] * 1000)} 注意:`list ${obj(.path.y)}`会将数组元素的值转换成一个只有一个元素的列表(不管它原来是什么)。这样,`lindex ... 0`就总是得到整个值。但是,如果原来的值是一个包含多个元素的列表,那么这样做就会把整个列表作为一个元素,而我们取第一个元素就是整个列表,这显然不对。 因此,这个方法不行。 所以,我们只能按照数据的实际情况来写代码。 如果数据结构不确定,我建议在外部处理: set yValue ${obj(.path.y)} if {[llength $yValue] >= 1} { set first [lindex $yValue 0] } else { set first 0 } -expr {round($first * 1000)} 这样,表达式就变得简单了。而且避免了在表达式中重复数组访问。 但是,如果这个表达式是必须内联的(比如在某个组件的配置选项中),那么可能无法拆分。此时,我们只能使用三元运算符。 因此,根据上下文,我们有两种优化方案: 方案一(当数据结构确定非空时): -expr {round([lindex ${obj(.path.y)} 0] * 1000)} 方案二(当数据结构可能为空时): -expr {[llength ${obj(.path.y)}]? round([lindex ${obj(.path.y)} 0]*1000) : 0} 注意:三元运算符中的表达式部分要用括号括起来,避免运算符优先级问题。 此外,如果`.path.y`的值是一个空字符串,那么`llength`会返回1(因为空字符串在Tcl中被视为一个元素的列表?不,实际上,空字符串作为列表时,`llength`返回0。因为空列表就是空列表。 测试: % set x "" % llength $x 0 所以,空字符串作为列表时,长度为0。 因此,方案二可以处理空列表。 结论: 请根据您的数据情况选择方案一或方案二。 另外,关于乘以1000后取整,如果是为了转换为整数(比如像素),那么也可以考虑使用`int`函数,但`round`是四舍五入,`int`是截断。根据需求选择。 最后,注意表达式中数值类型:如果`.path.y`的值是整数,乘以1000还是整数,但如果是浮点数,则要注意精度问题。`round`函数会四舍五入到最接近的整数。 所以,最终代码取决于具体需求和数据情况。 </think> ### 代码分析:`-expr {round([lindex $obj(.path.y) 0]*1000)}` 这段代码是 Tcl/Tk 脚本中的表达式,主要功能是从对象 `$obj` 的路径 `.path.y` 中提取第一个值,将其乘以 1000 后四舍五入取整。以下是详细分析和优化建议: --- #### **问题分析** 1. **语法错误**: - 原始代码 `$obj(.path.y)` 缺少数组索引的闭合引号或括号,正确写法应为 `$obj(.path.y)` 或 `${obj(.path.y)}`(若下标含特殊字符)。 - `lindex` 命令需要完整的列表作为参数。 2. **潜在风险**: - 若 `.path.y` 返回空列表,`lindex ... 0` 会报错 `list index out of range`。 - 若返回值非数值(如字符串),`*1000` 会导致类型错误。 3. **冗余操作**: - `lindex ... 0` 在仅需第一个元素时是合理的,但若数据源确保为单值列表,可直接简化。 --- #### **优化方案** ##### 1. **基础修正(语法+健壮性)** ```tcl -expr {round([lindex [set ${obj(.path.y)}] 0] * 1000)} ``` - **优化点**: - 使用 `set` 显式获取变量值,避免解析歧义。 - 添加空值保护(若数据可能为空): ```tcl -expr { set val [lindex ${obj(.path.y)} 0] if {$val ne ""} { round($val * 1000) } else { 0 } } ``` ##### 2. **性能优化(避免重复计算)** ```tcl set yVal [lindex ${obj(.path.y)} 0] -expr {round($yVal * 1000)} ``` - **优势**: - 分离数据获取与计算,减少 `obj` 的重复访问。 - 预存 `yVal` 便于复用。 ##### 3. **类型安全强化** ```tcl -expr { if {[string is double [set val [lindex ${obj(.path.y)} 0]]]} { round($val * 1000) } else { 0 ;# 默认值 } } ``` - **作用**:显式检查数值类型,避免非数字导致的崩溃。 ##### 4. **极端情况处理** 若数据源不稳定,添加完整异常捕获: ```tcl -expr { catch { round([lindex ${obj(.path.y)} 0] * 1000) } result ? 0 } ``` --- #### **关键改进总结** | **问题** | **优化措施** | **效果** | |-------------------------|---------------------------------------|-----------------------------| | 语法错误 | 显式调用 `${obj(.path.y)}` | 避免解析失败 | | 空列表风险 | 添加空值检查 `if {$val ne ""}` | 防止索引越界错误 | | 非数值类型风险 | 使用 `string is double` 验证 | 增强类型安全性 | | 重复访问 `obj` | 预存数据到变量 `yVal` | 提升执行效率 | | 未捕获异常 | 嵌套 `catch` 命令 | 保证脚本健壮性 | --- #### **最佳实践示例** ```tcl # 预存数据 + 空值检查 + 类型验证 set yPath ${obj(.path.y)} set firstVal [lindex $yPath 0] if {$firstVal ne "" && [string is double $firstVal]} { set result [expr {round($firstVal * 1000)}] } else { set result 0 } # 输出或使用 $result ``` --- ### 相关问题 1. 如何避免 Tcl 中 `lindex` 的索引越界错误? 2. Tcl 的 `expr` 表达式在处理浮点数时有哪些注意事项? 3. 除了 `round()`,Tcl 中还有哪些数值取整方法?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值