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;
}
posted @ 2025-10-05 19:11  XXh_Laoxu  阅读(14)  评论(0)    收藏  举报

转载请注明出处!


#页面摧毁游戏#
使用【上下左右】控制飞行器的运动
使用【空格】发射导弹
点击开始摧毁