专栏文章
题解:P10498 石头游戏
P10498题解参与者 2已保存评论 1
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 1 条
- 当前快照
- 1 份
- 快照标识符
- @mipb2kjm
- 此快照首次捕获于
- 2025/12/03 09:05 3 个月前
- 此快照最后确认于
- 2025/12/03 09:05 3 个月前
题目大意
根据题目模拟操作,输出 秒后石头最多的格子里有多少个石头。
分析
首先,,暴力枚举必然超时,我们发现,操作序列的长度极短,仅有 至 ,所以每 秒为一个周期,所有操作序列均会回到第一个操作,然后循环,故考虑矩阵快速幂加速递推。
先要考虑如何构建矩阵。
一、考虑放置操作
我们先将其简化一下:只考虑网格是一维,且每个格子上的操作也只有一种的情况。
假设我们输入的数据为:
CPP1 3 2 3
012
0
1
2
根据题目,我们可以知道,每秒钟我们在第一个格子上放 个石头,在第二个格子上放 个石头,在第二个格子上放 个石头。
然后让我们思考一下如何创建状态矩阵,三个时刻每一格石头的个数即为
0 0 0,0 1 2,0 2 4。我们发现每一秒某个格子上增加的石头数量都是一样的。
我们将这三个网格上的石头的数量设为 ,代表第 秒时的状态(转移前的状态), 表示第 秒时的状态(转移后的状态)。
通过分析我们可以列出下列表达式:
变换一下:
但是还要解决一个问题,那就是结尾加上去的 。
我们知道结尾加上的数是不会随着时间的变化而变化的,所以我们设 ,则:
将其与上面的式子结合我们就可以得到:
( 为 转移后的值)
这样我们就得到了状态矩阵:
和转移矩阵:
二、考虑移动操作
若当前状态矩阵为:
变化一下:
所以转移矩阵为:
矩阵的构建就实现完了。
三、考虑代码实现
设 。
把网格看做 的一维向量,定义 行 列的状态矩阵 ,下标为 至 ,其中 记录格子 中石头的个数。
特别地,令 始终等于 。
会随着时间的增长而不断变化。设 表示 秒之后的状态矩阵。
在游戏开始时,根据题意和 的定义,有:
注意到操作序列的长度不超过 ,而 至 的最小公倍数是 ,所以每经过 秒,所有操作序列都会重新处于最开始的字符处。我们可以统计出第 秒()各个格子执行了什么字符,第 秒执行的字符与第 秒一定是相同的。
对于 至 之间的每个 ,各个格子在第 秒执行的操作字符可以构成一个转移矩阵 ,矩阵行、列下标都为 至 。
构造方法如下:
-
若网格 第 秒的操作字符为
N,且 ,则令 ,表示将石头推至上方的格子。字符W,S与E类似。 -
若网格 第 秒的操作字符为数字 ,则令 ,表示该格中原有的石头不动,再拿 块石头。
-
令 。
-
令 剩余部分均为 。
上述构造实际上把状态矩阵的第 列作为"石头来源", 保证了 始终为 , 相当于从"石头来源"获取 个石头,放到格子 中。
使用矩阵乘法加速递推,遇到常数项时,经常需要在状态矩阵中添加一个额外的位置,始终存储常数 ,并乘上转移矩阵中适当的系数,累加到状态矩阵的其他位置。
四、考虑答案统计
设:
A = \displaystyle \prod_{i = 1}^{60} A_i $$
t = q \times 60 + r(0 \le r \lt 60)
F_t = F_0 \cdot A^q \cdot \displaystyle \prod_{i = 1}^r A_i
使用矩阵乘法和快速幂计算该式,$F_t$ 第 $1$ 至 $n \times m$ 列中的最大值即为所求。
## AC Code
```cpp
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 10, M = 65;
struct matrix{ // 矩阵
ll c[M][M];
matrix(){memset(c, 0, sizeof c);} // 生成函数:初始值均为0
};
int n, m, t, act, x, y;
ll mx;
int idx[N][N], now[N][N];
matrix cg[M], sum, ans;
char c, seq[N][N];
matrix operator*(matrix a, matrix b){ // 新定义矩阵乘
matrix u;
for(int i = 0; i <= n * m; i ++)
for(int j = 0; j <= n * m; j ++)
for(int k = 0; k <= n * m; k ++)
u.c[i][j] += a.c[i][k] * b.c[k][j];
return u;
}
int gt(int a, int b){return (a - 1) * m + b;} // 将网格中的点降维
matrix qpow(matrix a, int b){ // 矩阵快速幂
matrix u, x = a;
for(int i = 0; i < M; i ++)
u.c[i][i] = 1;
for(; b; b >>= 1){
if(b & 1) u = u * x;
x = x * x;
}
return u;
}
int main() {
scanf("%d %d %d %d\n", &n, &m, &t, &act);
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
scanf("%c", &c);
idx[i][j] = c - '0' + 1;
}
scanf("\n");
}
for(int i = 1; i <= act; i ++)
scanf("%s", seq[i]);
for(int tim = 1; tim <= 60; tim ++){ // 生成前60次转移矩阵
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
x = idx[i][j], y = now[i][j];
if(isdigit(seq[x][y])){ // 操作字符为数字
cg[tim].c[0][gt(i, j)] = seq[x][y] - '0'; // 从“石头来源”取x个石头
cg[tim].c[gt(i, j)][gt(i, j)] = 1; // 原来的石头数不变
}
// 操作字符为NWSE
if(seq[x][y] == 'N' && i > 1) cg[tim].c[gt(i, j)][gt(i - 1, j)] = 1;
if(seq[x][y] == 'W' && j > 1) cg[tim].c[gt(i, j)][gt(i, j - 1)] = 1;
if(seq[x][y] == 'S' && i < n) cg[tim].c[gt(i, j)][gt(i + 1, j)] = 1;
if(seq[x][y] == 'E' && j < m) cg[tim].c[gt(i, j)][gt(i, j + 1)] = 1;
// 该位操作位数+1
now[i][j] = (y + 1) % strlen(seq[x]);
}
}
cg[tim].c[0][0] = 1;
}
// 统计前60次转移
sum = cg[1];
for(int i = 2; i <= 60; i ++)
sum = sum * cg[i];
// 快速幂计算每60秒后的矩阵
ans.c[1][0] = 1;
ans = ans * qpow(sum, t / 60);
// 剩余的不到60次操作
for(int i = 1; i <= t % 60; i ++)
ans = ans * cg[i];
// 统计最大石头数
for(int i = 1; i <= n * m; i ++)
mx = max(mx, ans.c[1][i]);
printf("%lld\n", mx);
return 0;
}
```相关推荐
评论
共 1 条评论,欢迎与作者交流。
正在加载评论...