二分答案——时隔三个月的再次

暂且不提三个月未更新的主要原因(懒)

来自25年牛牛寒假营的一道寄巧题

image.png

Part 1: 单调性和二分法

仔细考虑这个问题,显然满足单调性:假设时间 $ T $ 恰好发生了第 $ k $ 次碰撞,那么在 $ T $ 之前不能发生任何碰撞,在 $ T $ 之后只会有更多的碰撞。

因此,我们回到了之前的二分答案模板,但仍需解决以下几个问题:如何简化问题?

对于高中物理而言,不需要赘述太多。对于质量相同的小球,只需将其视作相互穿过的模型。这样一来,加上时间 $ T $,每个小球的初始和最终状态都将变得明确得多。此时的问题就变成了判断这两个小球是否会碰撞。
image.png

//雨巨的ppt偷的图

Part 2: 双指针算法

考虑到上述情况,可以从第一个小球开始分析。(基于相对运动原理)只需判断该小球经过时间 ( 2T $ (速度均为1)后与下方的第一个小球之间的距离差是否小于 $ X $。若小于,则会发生碰撞。同理,继续检查下一个蓝色小球及其对应的黄色小球,直到蓝色小球能够与当前最后一个黄色小球发生碰撞为止。此后,再依次考虑第二个蓝色小球的情况。显然,第二个蓝色小球一定会与前一个已经发生的碰撞事件中的最靠后的那个黄色小球发生碰撞(前提是当前蓝色小球前方没有其他黄色小球)。

为了高效地解决问题,我们可以使用双指针技术。具体来说,设置两个指针 $ p1 $ 和 $ p2 $:

  • $ p1 $ 指向当前正在考察的蓝色小球。
  • $ p2 $ 指向能与当前蓝色小球发生碰撞的最后一颗黄色小球。

在外层循环中,逐步移动 $ p1 $ 指针指向下一个蓝色小球。而在内层循环中,不断向前推进 $ p2 $ 指针,寻找能与当前 $ p1 $ 小球发生碰撞的最大索引 $ p2 $。一旦找到了合适的 $ p2 $,就可以计算出在这个区间内的碰撞次数。

这种方法的优点在于内层循环不会重复访问已经处理过的元素,从而提高了效率。具体流程如下:

  1. 初始化 $ p1 = 0 $ 和 $ p2 = 0 $。
  2. 对于每个 $ p1 $,不断增加 $ p2 $,直到不再满足碰撞条件。
  3. 更新总碰撞次数 $ res += (p2 - p1) $。
  4. 移动 $ p1 $ 到下一个小球,重复步骤2和3,直至所有蓝色小球都被处理完毕。

通过这种方式,我们可以有效地减少不必要的比较操作,使得整个算法的时间复杂度降低至线性级别。

Image Description

Part 3: 计算碰撞次数

设 ( p1 $ 表示第一个位于当前位置右侧的小球,$ p2 $ 则表示能与当前小球发生碰撞的最后一颗小球。对于每个 $ p1 $,其要发生碰撞的前提条件是它必须位于当前小球 $ i $ 的右侧。而对于 $ p2 $,则是所有能与当前小球发生碰撞的最后一颗小球,即满足 $ v[p2] \leq i + T $。因此,总的碰撞次数可计算为 $ res += (p2 - p1) $。

Part 4: 细节问题

由于上述讨论仅限于整数范围内,实际上可能存在两个小球之间距离仅为1单位长度的情况下,它们可以在仅仅0.5秒内相遇。鉴于此题对精度的要求较高,此处的时间 $ T $ 实际上被乘以了2倍。因此,在处理结果时需要注意这一细节问题。

    #include<bits/stdc++.h>
#define N 1005
#define mod 998244353
#define int long long
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f3f;
ll check(ll t,vector<ll> &q,vector<ll> &p)
{
    ll res = 0;
    ll p1 = 0,p2 = 0;
    for(auto &i : q)
    {
        while(p1 < p.size() && p[p1] < i)   p1++;//p1记录i右边的第一个小球
        while(p2 < p.size() && p[p2] <= i + t)  p2++;//p2记录能与当前小球碰撞的最后一个小球
        res += p2-p1;
    }
    return res;
}
void solve()
{
    vector<ll> q1,q2;
    ll n,k;
    cin>>n>>k;
    //int ans = 0;
    for(int i = 0;i < n;i++)
    {
        int q,p;
        cin>>q>>p;
        if(p == 1)  q1.push_back(q);
        else    q2.push_back(q);
    }
    sort(q1.begin(),q1.end());
    sort(q2.begin(),q2.end());//题目并未保证小球坐标升序排列
    int l = 0, r = INF,ans = 0;
    while(l <= r)
    {
        int mid = (l+r)/2;
        if(check(mid,q1,q2) >= k)
        {
            r = mid-1;
        }
        else
        {
            ans = mid;
            l = mid+1;
        }
    }
    //double ans1 = ans/2;//注意这里的ans实际上是两倍的T
    if(ans == INF)  cout<<"No\n";
    else    {
        ans++;
        cout<<"Yes\n"<<ans/2;
        if(ans&1)   cout<<".500000";
        else    cout<<".000000";
        //printf("Yes\n%d.%c00000\n",ans/2,"05"[ans&1]);
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int T = 1;
    while(T--)
      solve();
    return 0;
}
posted @   graspppp  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示