题目描述
难度分:1354
输入n(1≤n≤1018)。
你有一个整数x,初始值为1。
你有一个六面的骰子,可以等概率地掷出1~6。
不断重复如下操作,直到x≥n为止:
- 掷骰子,把x乘上骰子显示的数字。
问:当你停止操作时,x恰好等于n的概率是多少?
设概率为ab,输出分数取模的结果,其中mod=998244353。
输入样例1
6
输出样例1
239578645
输入样例2
7
输出样例2
0
输入样例3
300
输出样例3
183676961
输入样例4
979552051200000000
输出样例4
812376310
算法
概率DP
一眼就是概率DP
,比较典。
状态定义
dp[x]表示从x扔到n的概率,显然答案就是dp[1]。
状态转移
每次扔骰子可以扔出6个点数,状态转移方程为
dp[x]=Σ6p=1dp[p×x]6
此时发现如果一直掷出点数1,就会无限依赖,因此把状态转移方程变形为
dp[x]=Σ6p=2dp[p×x]5
这样就可以做了,最后边界条件为:当x>0时,dp[x]=0,当x=n时,dp[x]=1。
复杂度分析
时间复杂度
每一轮都有5个分支,可以发现,只要没有1这个分支,复杂度就是logn级别的(因为x×py=n,初始情况下x=1,那么y就是logpn),只是底数为[2,6]。因此,整体的时间复杂度为O(logn)。
空间复杂度
采用记忆化搜索来实现DP
,递归深度为logn级别,状态数量也是这个级别。因此,额外空间复杂度也为O(logn)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long LL;
const int MOD = 998244353, inv5 = 598946612;
LL n;
int dfs(LL x, unordered_map<LL, int>& dp) {
if(x > n) return 0;
if(x == n) return 1;
if(dp.find(x) != dp.end()) return dp[x];
LL res = 0;
for(int p = 2; p <= 6; p++) {
res = (res + dfs(x*p, dp)) % MOD;
}
res = res*inv5%MOD;
return dp[x] = res;
}
int main() {
scanf("%lld", &n);
unordered_map<LL, int> dp;
printf("%lld\n", dfs(1, dp));
return 0;
}