题解:P14508 猜数游戏 guess

· · 题解

Blog

赛时大脑获得了犯困 debuff,成功宕机,11:30 才想出了这题。

看上去比较像交互最优化题,可以考虑一下对策略进行 DP。注意到一段区间的答案只与其长度有关,而与其具体对应的下标区间无关,因此定义 dp_{i} 表示长度为 i 的区间所需的最少询问次数。

因为我们的一般策略是没遇到目标的时候一直走,遇到目标了折返回来,所以实际上转移的关键就在于如何决定每一次折返后该选择走什么长度。先考虑一个暴力转移,考虑枚举折返时选择的长度 l,那么转移的代价就分为两部分:

因此转移方程为 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)}。具体而言:

注意到这张图是个稠密图,因此跑堆优化 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;
}