洛谷 P12078 [OOI 2025] Best Runner 题解

Solution

首先分析每位跑者的最优决策,尝试寻找一下特殊性质。

首先,直觉告诉我们,在不同跑道间切换的过程中一定不会改变方向。假设最优策略中改变了方向,最终跑过了 \(l\sim r\) 这些跑道。而一开始就直接一步步切换到这其中最短的跑道并在它上面一直跑下去一定不会更劣。

其次,最优策略一定是往一个方向切换若干条跑道后,在最后一条跑道一直跑,且它一定是这些跑道中最短的。用反证法易证:假设最短的不是最后一条,那我们直接提前在最短的那里停下一直跑一定更优。

\(L(i)\) 为第 \(i\) 条跑道左边第一个比它短的跑道下标,\(R(i)\) 同理。根据前面的结论可知,初始时位于跑道 \(i\) 的跑者最终只可能停在 \(i,L(i),L(L(i)),\dots\)\(R(i),R(R(i)),\dots\)

因此我们直接用单调栈预处理出 \(L(i)\)\(R(i)\),再暴力枚举所有可能的位置计算最优的答案。注意用前缀和计算 \(a_i\) 的区间和。这就有了这份代码:

#include <iostream>
#define rept(i,a,b) for(int i(a);i<=b;++i)
#define pert(i,a,b) for(int i(a);i>=b;--i)
#define int long long  // 注意开long long
using namespace std;
constexpr int N=3e5+5;
int a[N],b[N],s[N],l[N],r[N],stk[N],top;
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int n,m,T,ans=0;
    cin>>n>>m>>T;
    rept(i,1,n){
        cin>>a[i];
        s[i]=s[i-1]+a[i];
    }
    rept(i,1,m){
        cin>>b[i];
    }
    rept(i,1,n){
        while(top&&a[stk[top]]>=a[i]) --top;
        l[i]=stk[top];
        stk[++top]=i;
    }
    top=0;
    pert(i,n,1){
        while(top&&a[stk[top]]>=a[i]) --top;
        r[i]=stk[top];
        stk[++top]=i;
    }
    rept(i,1,m){
        int p=b[i];
        while(p){
            ans=max(ans,b[i]-p+(T-s[b[i]]+s[p])/a[p]);
            p=l[p];
        }
        p=b[i];
        while(p){
            ans=max(ans,p-b[i]+(T-s[p-1]+s[b[i]-1])/a[p]);
            p=r[p];
        }
    }
    end:
    cout<<ans;
    return 0;
}

这种优雅的暴力在随机数据上表现相当不错,能获得 90 分。然而它卡不过 Subtask 2。

其实构造 Hack 很简单,只要随便搞个单峰序列例如 \(1,2,3,4,3,2,1\) 就能把它卡到 \(O(n^2)\)。但是我们还有最后一招——乱搞。

考虑卡时,在即将 TLE 时输出当前得到的最优解。这种做法大概率是正确的,但取决于数据强度。这样就可以 AC 啦!

注:本篇题解的做法并不具有绝对正确性,仅能通过较弱的测试数据。然而这种卡时的思想具有一定的启发性,例如某些随机化或模拟退火的题目就我们就可以尝试多换几个随机种子,不断运行算法直到即将超时,这样可以有更大的概率得到正确答案。

AC Code

#include <iostream>
#include <chrono>
#define rept(i,a,b) for(int i(a);i<=b;++i)
#define pert(i,a,b) for(int i(a);i>=b;--i)
#define int long long  // 注意开long long
using namespace std;
constexpr int N=3e5+5;
int a[N],b[N],s[N],l[N],r[N],stk[N],top;
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    auto start=chrono::high_resolution_clock::now(); 
    int n,m,T,ans=0;
    cin>>n>>m>>T;
    rept(i,1,n){
        cin>>a[i];
        s[i]=s[i-1]+a[i];
    }
    rept(i,1,m){
        cin>>b[i];
    }
    rept(i,1,n){
        while(top&&a[stk[top]]>=a[i]) --top;
        l[i]=stk[top];
        stk[++top]=i;
    }
    top=0;
    pert(i,n,1){
        while(top&&a[stk[top]]>=a[i]) --top;
        r[i]=stk[top];
        stk[++top]=i;
    }
    rept(i,1,m){
        int p=b[i];
        while(p){
            ans=max(ans,b[i]-p+(T-s[b[i]]+s[p])/a[p]);
            p=l[p];
            auto stop=chrono::high_resolution_clock::now();  
            auto ms=std::chrono::duration_cast<std::chrono::milliseconds>(stop-start).count();  
            if(ms>900) goto end;
        }
        p=b[i];
        while(p){
            ans=max(ans,p-b[i]+(T-s[p-1]+s[b[i]-1])/a[p]);
            p=r[p];
            auto stop=chrono::high_resolution_clock::now();  
            auto ms=std::chrono::duration_cast<std::chrono::milliseconds>(stop-start).count();  
            if(ms>900) goto end;
        }
    }
    end:
    cout<<ans;
    return 0;
}
posted @ 2025-07-24 18:51  xiaoniu142857  阅读(11)  评论(0)    收藏  举报