HDU 5945 Fxx and game
“错题集”第一篇。
题意
给你三个数 \(X\),\(k\),\(t\),有两种操作:
- \(X\leftarrow X-i\),\(i\in[0,t]\);
- \(X\leftarrow\frac{X}{k}\),\(k|X\)。
问你通过这两种操作把 \(X\) 变成 \(1\) 最少要多少步。
\(1\le X,k\le10^6\),\(0\le t\le10^6\)。
解法
Hint 1
先考虑暴力怎么做?Hint 2
通过暴力的做法,在 $X$ 的变化中有无规律可循?Solution
考虑 dp。令 $dp_i$ 表示将 $i$ 变成 $1$ 的最小代价。容易发现 $dp_i$ 只能由 $dp_{i-j}$ 和 $dp_{i\div k}$ 转移来,其中 $0\le j\le t$。但是枚举 $X$ 和 $t$ 的时间复杂度会爆,那么是否有一些 dp 状态是我们不用枚举的呢?答案是肯定的(不然怎么做)。由于我们要求的是最小值,而我们写出来的 $dp_i=dp_{i-j}+1$ 的方程当中我们只会取 $dp_{i-j}$ 中最小的那个值去更新 $dp_i$,所以我们可以用单调队列优化。具体的,当队列中队首与当前枚举的 $i$ 的差大于 $t$ 时,就把这个队首弹出去;如果队尾的 $dp$ 值已经没有当前 $dp_i$ 小就弹队尾。然后转移的时候用队首转移即可。至于 \(dp_{i\div k}\),特判一下是否满足就行了。
Code
#include <bits/stdc++.h>
#define loop(i,a,b) for(int i=(a);~i;i=(b))
#define eb emplace_back
#define pb push_back
using namespace std;
typedef long long ll;
constexpr int N = 1e6 + 15, inf = 0x3f3f3f3f;
int x, k, t;
int dp[N];
int q[N], hh, tt;
void solve () {
memset (dp, 0x3f, sizeof dp);
scanf ("%d %d %d", &x, &k, &t);
hh = 0, tt = -1;
q[++ tt] = 1;
dp[1] = 0;
for (int i = 2; i <= x; ++ i) {
if (i % k == 0) dp[i] = min (dp[i], dp[i / k] + 1);
while (hh < tt && q[hh] < i - t) ++ hh;
dp[i] = min (dp[i], dp[q[hh]] + 1);
while (hh < tt && dp[q[tt]] > dp[i]) -- tt;
q[++ tt] = i;
}
printf ("%d\n", dp[x]);
}
int main () {
int T;
scanf ("%d\n", &T);
while (T --) solve ();
return 0;
}

浙公网安备 33010602011771号