贪心、二分答案

答案显然具有单调性,考虑二分答案 $mid$,难点在于检查。检查时不可提前选定 $p$,应该判定是否存在能够在 $mid$ 次攻击内杀死 $k$ 个敌人的 $p$ 作为攻击点

对于血量为 $h_i$,位置为 $x_i$ 的敌人 $i$,若其在 $mid$ 次攻击后死亡,能够将其击杀的攻击点 $p$ 必定满足 $\dfrac{h_i}{m - |p - x_i| } \leq mid$,这个式子由于 $p$ 的值不固定而难以分析。转换思路,因为 $mid$ 为定值,将 $mid$ 换到分母上得到 $\dfrac{h_i}{mid} \leq m - |p - x_i|$,又因为 $m$ 为定值,将式子化为 $|p - x_i| \leq m - \dfrac{h_i}{mid}$。能够看出对于给定的值 $mid$,能够在至多 $mid$ 次攻击内击杀 $i$ 的位置 $p$ 形成一段连续区间,为了方便计算再给 $\dfrac{h_i}{mid}$ 套一个上取整,因为 $p$ 是整数,这不影响答案计算。最终得到如下式子。

$$|p - x_i| \leq m - \left\lceil \dfrac{h_i}{mid} \right\rceil$$

首先排除不能在至多 $mid$ 次攻击内杀死的敌人。对于能够在至多 $mid$ 次攻击内杀死的敌人,第 $i$ 个敌人贡献了一个区间 $[l_i,r_i]$,如果选定的 $p \in [l_i,r_i]$ 就可以杀死第 $i$ 个敌人。

问题转化为对于序列 $a$ 执行若干次区间 $[l,r]$ 加 $1$ 操作,所有操作结束后判定是否存在 $a_i \geq k$,离散化处理显然可行,但这里存在更简单的处理手法。

具体地,从左到右扫描序列,只考虑所有满足 $i = l$ 或 $i = r+1$ 的位置 $i$,因为只有这些位置可能满足 $a_i \neq a_{i-1}$,同时维护当前位置线段覆盖数 $cnt$(最初为 $0$)。如果存在 $x$ 个满足 $i = l$ 的线段和 $y$ 个满足 $i = r+1$ 的线段,就令 $cnt \leftarrow cnt + x - y$,若某一时刻 $cnt \geq k$ 则说明存在合法的 $p$ 作为攻击点。

总体时间复杂度 $O(n \log n \log V)$。

#include<bits/stdc++.h>
using namespace std;
constexpr int inf=1'000'000'000;
int n,k,ans,h[100'005],p[100'005];
long long m;
bool fail(int x){
    map<int,int> w;
    for(int i=1;i<=n;i++) if(h[i]<=x*m){
        int dis=m-(h[i]+x-1)/x;
        ++w[p[i]-dis],--w[p[i]+dis+1];
    }
    int cnt=0;
    for(auto [i,x]:w) if((cnt+=x)>=k) return 0;
    return 1;
}
void solution(){
    cin>>n>>m>>k,ans=-1;
    for(int i=1;i<=n;i++) cin>>h[i];
    for(int i=1;i<=n;i++) cin>>p[i];
    int l=1,r=inf;
    while(l<=r){
        int mid=(l+r)>>1;
        if(fail(mid)) l=mid+1;
        else ans=mid,r=mid-1;
    }
    cout<<ans<<'\n';
}
int T;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>T;
    while(T--) solution();
    return 0;
}
最后修改:2024 年 11 月 22 日
如果觉得我的文章对你有用,请随意赞赏