CodeForces #431 849D Rooter's Song 技巧题

做题之前,首先

    骑士团参见公主殿下!!


恩。正题

题意是说给出n个位于x轴正方向和y轴正方向上的点,第i个点在ti秒后将会移动,移动方向是假若在x轴上就向y轴正方向垂直移动,反之在y轴上就向x轴正方向水平移动。当两个点相撞时,水平移动的点的速度立即改为竖直向上,竖直移动的点立即改为水平向右。所有的点的速率均为1单位/ms。有两条直线x=w和y=h,当点打在这两条直线中的任意一条时便不再移动。

题目给出n,w,h以及每个点的初始位置和ti,求出每一个点的最后的位置。


怎么说呢,这题我确实不会做,我看了题解才过的。确实这道题目我只能归结为技巧题,除此之外我也不知道算啥。

首先这道题的关键点在于点会相撞。假设所有的点都不会相撞,那么这道题就是简单情形,接下来我们来考虑相撞。

我们考虑两个在下1S会相撞的两个点,会发现他们同处于一条直线上,x+y=k,这里k是某个数

此外,不在这条x+y=k的直线上的点,永远都不可能和这两个点相撞,因为速率是一样的,需要证明请留言

这就暗示着,对于相撞的情况,我们把所有点按照某个条件划分为若干组,我们保证每一组内的点绝对不会和其他的组的点相撞,那么对于整体来说,就是不相撞的情况了,也就意味着,我们只需要处理一组内的相撞即可。

这个某个条件就是在上面说的直线的k。

首先我们把等待时间ti去掉,对于每个点,我们都把时间往回退ti秒,此时每个点的坐标不是(-ti,p[i]),就是(p[i],-ti),那么就按照p[i]-ti划分为若干组即可。

这样一来,一组内的点是会相撞的,并且,该组的点绝对不会和其他的组的点相撞,因为k不一样。


接下来处理一组内相撞的情况。

实际上我们能知道该组所有点最后的位置,只是不知道那个点对应哪个位置而已(把相撞看成穿透,对最后的位置没有影响,但是能够很轻易让我们得到最后的位置)

接下来我会阐明一个点对应哪个位置

请想象你的两根手指,一根手指从(0,h)水平划到(w,h)再竖直向下滑到(w,0)

另一根手指从(0,h)划到(0,0)再水平划到(w,0)

那么点和位置的对于关系就是,第一根手指划到的第一个答案位置对于你另一根手指划到的第一个点,划到的第二个答案位置对于划到的第二个点.....

我在附录给出证明

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>

using namespace std;
const int maxm=100010;


unordered_map<int,vector<int>> uma;
int n,w,h,g[maxm],p[maxm],t[maxm],ansx[maxm],ansy[maxm];
vector<int> vx,vy;

int main(){
    ios_base::sync_with_stdio(0);
    cin>>n>>w>>h;
    for(int i=0;i<n;++i){
        cin>>g[i]>>p[i]>>t[i];
        uma[p[i]-t[i]].push_back(i);
    }
    for(auto be=uma.begin();be!=uma.end();++be){
        for(const auto& x:be->second)
            (g[x]==1?vx:vy).push_back(p[x]);
        sort(vx.begin(),vx.end());
        sort(vy.begin(),vy.end());
        sort(be->second.begin(),be->second.end(),[](int x,int y){
             return g[x]!=g[y]&&g[x]==2||g[x]==g[y]&&(g[x]==2&&p[x]>p[y]||g[x]==1&&p[x]<p[y]);
             });
        for(int i=0,j=be->second[i];i<vx.size();j=be->second[++i])
            ansx[j]=vx[i],ansy[j]=h;
        for(int i=0,j;i<vy.size();++i)
            j=be->second[i+vx.size()],ansx[j]=w,ansy[j]=vy[vy.size()-i-1];
        vx.clear();vy.clear();
    }
    for(int i=0;i<n;++i)
        cout<<ansx[i]<<' '<<ansy[i]<<endl;
    return 0;
}


附录:

有关于手指那样移动所得到的点和答案位置的对应关系的正确性证明

一开始,直线x+y=k上存在许多点

无论内部的点如何发生相撞,对于x值最大和最小的那两个点来说,一旦被撞改变方向,就没有可能再次发生撞击使得这两点改变方向,对于x最大的点来说,会飞到x=w线上最后一个答案位置,x最小的点来说会飞到y=h线上第一个答案位置。并且可以得出,这两点一定是x+y=k线上最先打到边界线上的点。当这两个边界点飞走后,新的边界点也是按照同样的分析,最后即可以得出这个结论。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值