3 条题解
-
2
玩家需要从 号点移动到 号点;我们可以反向思考,求从目标位置 回退到起点 的最少步数。由于题目保证 且要求最少的使用次数,在可能的情况下,优先使用“反向倍数骰子”(即整除 )能够最快地缩小距离。
- 特殊情况 :此时倍数骰子(直达 号点)失去了加速作用,玩家只能全程使用指定骰子。从起点 到 的总距离为 ,而指定骰子每次最多移动 步,因此最少次数为 。
- 一般情况 :当当前位置 时,通过循环处理以下三种状态:
- :此时无法再使用倍数骰子(因为起点必须是正整数),只能依靠指定骰子从 一步步移动过来。所需的最少步数为 ,累加后即可结束计算。
- :说明在正向移动时,到达当前 号点的最后一段是通过指定骰子走完的。我们将其退回到最近的 的倍数,需要回退的距离为 。所需次数为 ,然后将 更新为这个最近的倍数。
- :此时可以直接使用逆向的倍数骰子,将 除以 ,计作使用了 次骰子。
由于每次循环都能将 减小到 的倍数并除以 (),循环执行的最大次数约为 次。
-
1
Sam的大富翁 题解
题目分析
从
1出发,每次有两种操作:- 使用加法骰子,可以让当前数字增加
1~k; - 使用乘法骰子,可以让当前数字变成原来的
p倍。
要求到达
E的最少操作次数。
1. DP
当
E比较小时,可以直接设:从
i出发,可以转移到:以及:
所以直接枚举即可,复杂度为
O(Ek)。代码中的
solve(E)就是这个 DP:void solve(int E){ for(int i=1;i<=E;i++) dp[i]=INF; dp[1]=0; for(int i=1;i<=E;i++){ for(int j=1;j<=k&&i+j<=E;j++) dp[i+j]=min(dp[i+j],dp[i]+1); if(i*p<=E) dp[i*p]=min(dp[i*p],dp[i]+1); } }
2.
当
E很大时,不能直接 DP。考虑最后一次使用乘法。假设乘法前到达:
那么做一次乘法后:
由于:
其中:
所以乘法之后还差
r,只需要再进行:次加法。
因此,如果采用这种方案,答案就是:
其中
+1表示最后进行的那一次乘法。于是可以递归:
int solveb(int E){ if(E<=100005){ solve(E); return dp[E]; } int q=E/p; int r=E%p; int need=solveb(q)+1; if(r) need+=(r+k-1)/k; return need; }这里的好处是每次递归都会变成
E/p,所以E会快速变小,最多只有O(log_p E)层。
3. 为什么
q=E/p这里利用了题目的条件:
假设最后一次乘法前到达的是比
E/p更小的数,例如q-1。把它提高到
q,最多只需要一次加法;但是乘法以后:增加了
p,而一次加法最多只能增加k,由于p>k,因此这样不会让答案变差。所以最后一次乘法之前可以直接取最大的:
4. 还要考虑完全不乘
上面的递归方案默认使用了乘法,但有的同学就会说了:欸!直接加法会不会比乘法更优?实际上是不会的,因为, 所以在能出现走p的情况下,肯定是走p的,否则刚刚的式子也能成立
特别地,当
p=1时,乘法相当于不动,所以直接只考虑加法。完整代码
#include<bits/stdc++.h> using namespace std; const int B = 5000; const int INF=1e9; long long k, p; #define int long long int dp[100005]; void solve(int E) { for(int i=1;i<=E;i++) { dp[i]=INF; } dp[1]=0; for(int i = 1; i <= E; i++){ for(int j = 1; j <= k && i + j <= E; j++) { dp[i + j] = min(dp[i + j], dp[i] + 1); } if(i * p <= E) { dp[i * p] = min(dp[i * p], dp[i] +1); } } } // } // int solveb(int E){ // if(E <= 100005){ // solve(E); // return dp[E]; // } // int need = solveb(E/p); // int k1 = E/p; // need += (k1 + k - 1) / k + 1; // return need; // } int solveb(int E){ if(E<=100004){ solve(E); return dp[E]; } int q=E/p; int r=E%p; int need=solveb(q); need+=1; if(r) need+=(r+k-1)/k; return need; } signed main(){ freopen("dice.in", "r", stdin); freopen("dice.out", "w", stdout); long long E; cin >> k >> p >> E; if (p == 1) { if (E == 1) { cout << 0; return 0; } cout << (E - 2) / k + 1; return 0; } cout << solveb(E); return 0; }复杂度:
E<100005时为O(Ek);E很大时,每次递归除以p,复杂度约为O(log_p E)。 - 使用加法骰子,可以让当前数字增加
- 1
信息
- ID
- 2735
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 4
- 标签
- (无)
- 递交数
- 79
- 已通过
- 10
- 上传者