FG操作
解题思路
这道题目要求我们计算在给定的数字序列上,通过一系列F(加法取模)和G(乘法取模)操作,最终得到0到9中每个数字的可能操作方式的数量。由于操作顺序的不同会导致不同的结果,我们需要动态规划来高效地统计所有可能的操作路径。
-
问题分析:
- 每次操作都是对当前序列的最左端两个数字进行操作,并将结果放回左端,直到序列长度为1。
- 每一步操作有两种选择:F(加法取模)或G(乘法取模)。
- 我们需要统计所有可能的操作路径,最终得到0到9每个数字的出现次数。
-
动态规划:
- 使用动态数组
prev_dp
来记录当前数字可能的值及其对应的路径数。 - 初始时,
prev_dp
中只有第一个数字的位置为1(因为初始序列只有第一个数字)。 - 对于后续的每个数字,我们根据当前可能的数字值(即
prev_dp
中的非零项),分别计算F和G操作后的新数字,并更新到curr_dp
中。 - 每次处理完一个数字后,将
curr_dp
复制到prev_dp
,以便处理下一个数字。
- 使用动态数组
-
复杂度分析:
- 时间复杂度:O(N * 10),其中N是数字序列的长度。对于每个数字,我们最多处理10种可能的当前数字值。
- 空间复杂度:O(1),只使用了固定大小的数组(10个元素)来存储中间结果。
代码注释
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl "\n"
const int INF = 0x7fffffff;
const int mod = 998244353;
const int N = 1e5+5;
int n;
int a[N];
int prev_dp[10]; // 前一个状态的动态规划数组,记录当前可能的值及其路径数
int curr_dp[10]; // 当前状态的动态规划数组,用于临时存储更新后的值
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i]; // 输入数字序列
// 初始化prev_dp,初始状态下只有第一个数字a[1]的路径数为1
memset(prev_dp, 0, sizeof(prev_dp));
prev_dp[a[1]] = 1;
for(int i = 2; i <= n; i++) { // 从第二个数字开始处理
memset(curr_dp, 0, sizeof(curr_dp)); // 清空当前状态的数组
for(int j = 0; j <= 9; j++) { // 遍历0到9的所有可能当前值
if(prev_dp[j] == 0) continue; // 如果当前值j的路径数为0,跳过
int f = (j + a[i]) % 10; // 计算F操作后的新值
int g = (j * a[i]) % 10; // 计算G操作后的新值
// 更新curr_dp中的路径数,取模防止溢出
curr_dp[f] = (curr_dp[f] + prev_dp[j]) % mod;
curr_dp[g] = (curr_dp[g] + prev_dp[j]) % mod;
}
// 将curr_dp的值复制到prev_dp,准备处理下一个数字
memcpy(prev_dp, curr_dp, sizeof(curr_dp));
}
// 输出最终结果,即prev_dp中0到9的路径数
for(int i = 0; i <= 9; i++) {
cout << prev_dp[i] << endl;
}
return 0;
}
代码解释
- 输入处理:读取数字序列的长度
n
和序列a
。 - 初始化:
prev_dp
数组初始时只有a[1]
的位置为1,表示初始状态下只有第一个数字的路径数为1。 - 动态规划处理:
- 对于每个数字
a[i]
,清空curr_dp
数组。 - 遍历
prev_dp
中的每个可能值j
,计算F和G操作后的新值f
和g
,并更新curr_dp
中的路径数。 - 将
curr_dp
的值复制到prev_dp
,以便处理下一个数字。
- 对于每个数字
- 输出结果:最终
prev_dp
中的值即为0到9每个数字的路径数,按顺序输出。
这种方法高效地利用了动态规划来避免重复计算,确保了在O(N)时间内解决问题。