Luogu P14508 猜数游戏 guess 题解 [ 蓝 ] [ 最短路 ] [ 线性 DP ]
赛时大脑获得了犯困 debuff,成功宕机,11:30 才想出了这题。
看上去比较像交互最优化题,可以考虑一下对策略进行 DP。注意到一段区间的答案只与其长度有关,而与其具体对应的下标区间无关,因此定义 \(dp_{i}\) 表示长度为 \(i\) 的区间所需的最少询问花费。
因为我们的一般策略是没遇到目标的时候一直走,遇到目标了折返回来,所以实际上转移的关键就在于如何决定每一次折返后该选择走什么长度。先考虑一个暴力转移,考虑枚举折返时选择的长度 \(l\),那么转移的代价就分为两部分:
- 走完后的代价:\(\max\{dp_l, dp_{i - l}\}\)。
- 从端点开始位移 \(j\) 的代价 \(dis_j\)。
因此转移方程为 \(dp_i\gets \min\limits_{1\le l \le \min\{i - 1, V\}}\{\max\{dp_{l}, dp_{i - l} \}+dis_l\}\)。
发现我们只需要求解 \(dis_j\) 就行了。一个平凡的思路是把数轴上的每个点看做图上的点,然后暴力连边,图论建模,从 \(0\) 开始跑最短路。但容易发现实际上有效的点只有 \(\bm{O(2V)}\) 个。具体而言:
- 对于 DP 过程中的查询:只会查询 \(0\le j \le V\) 的部分。
- 对于最短路过程中的转移:容易发现我们可以任意交换操作的顺序,而不影响最终的终点。因此在当前位置 \(> V\) 时,我们优先执行向小的数走,在当前位置 \(\le V\) 的时候,我们优先执行向大的数走。因为每条边最多跨过的点就是 \(O(a_i)\) 级别的,所以这样交换操作的顺序,我们一定不会走出 \(2000\) 这个上界。直接对这 \(2000\) 个点跑最短路即可。
注意到这张图是个稠密图,因此跑堆优化 Dijkstra 是不优的(堆优化的复杂度与边数相关),比朴素 Dijkstra 多一个 \(\log\),但实测堆优化还是比朴素 Dijkstra 跑的快。
时间复杂度 \(O(T(nV+V^2))\)。
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
using pi = pair<int, int>;
using pl = pair<int, ll>;
const int N = 10005, M = 1005, V = 2005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll n, m, a[N], b[N];
ll dis[V], dp[N];
bool legal(ll x)
{
return (0 <= x && x < V);
}
void solve()
{
cin >> n >> m;
ll amx = 0;
for(int i = 1; i <= m; i++)
{
cin >> a[i];
amx = max(amx, a[i]);
}
for(int i = 1; i <= m; i++) cin >> b[i];
memset(dis, 0x3f, sizeof(dis));
dis[0] = 0;
bitset<V> vis;
for(int r = 0; r < V; r++)
{
int u = -1;
for(int i = 0; i < V; i++)
{
if(vis[i]) continue;
if(u == -1 || dis[u] > dis[i]) u = i;
}
if(u == -1 || dis[u] >= inf) break;
vis[u] = 1;
for(int i = 1; i <= m; i++)
{
int v = u + a[i];
if(legal(v) && dis[v] > dis[u] + b[i])
dis[v] = dis[u] + b[i];
v = u - a[i];
if(legal(v) && dis[v] > dis[u] + b[i])
dis[v] = dis[u] + b[i];
}
}
memset(dp, 0x3f, sizeof(dp));
dp[1] = 0;
for(int i = 2; i <= n; i++)
{
for(int j = 1; j <= min(i - 1ll, amx); j++)
{
ll res = max(dp[j], dp[i - j]) + dis[j];
dp[i] = min(dp[i], res);
}
}
if(dp[n] >= inf) cout << "-1\n";
else cout << dp[n] << "\n";
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int sid, t;
cin >> sid >> t;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号