题解:P5819 【L&K R-03】音游大计算

· · 题解

Description

你需要实现一个音游判定,但是玩家是只会单指的奥托普雷先生。

话说什么粪谱物量 114514 啊?

Solution

敢开这道题的应该都能看出是线段树吧

首先把所有出现过的横坐标离散化,最终化为一个只有横坐标的判定线,有点像中二节奏的判定,有的键大,有的小,对于每个线段树的结点开一条链表,表示该区间点击时一定会被判定的按键编号。

链表代码:

int head[maxn<<6],tail[maxn<<6],chaintot;
int nxt[maxn<<7],uid[maxn<<7],pre[maxn<<7];
void push(int st,int u){
    if(!head[st])head[st]=tail[st]=++chaintot;
    else nxt[tail[st]]=++chaintot,pre[chaintot]=tail[st],tail[st]=chaintot;
    uid[chaintot]=u;return;
}

每次点击只会对可能产生判定效果的 key 中位置(纵向高度)最低(离屏幕顶端最远)的一个产生判定效果。

可是连 rks=12.22 的 yyw 都知道在 phigros 的 stasis 谱面中存在大量纵连,显然我们要先判定先落下的按键。

因此我们先对所有按键进行排序,按顺序插入即可。

开一个结构体排序,由于会爆精度,我们把每个时间映射成一个整数。

代码:

struct Key{int t;double aa,bb;int a,b;}key[maxn<<1];

for(int i=1;i<=n;++i){
        cin>>dub>>key[i].aa>>key[i].bb;
        key[i].t=dub*1e5+0.5;//映射 
    }

inline bool cmp(Key drag,Key flick){return drag.t<flick.t;}

sort(key+1,key+1+n,cmp);

如果我先加入了所有按键再直接处理的话,会遇到一个问题,我们联想到 phigros 中山茶花的某张 AT 铺面,存在双押三押交替的情况,而这里的玩家是不会双押多押的,如图。

那么根据先判 miss 的规则,太麻烦了。

不妨定义事件有三种类型。

1.音符进入判定区域。定义为 insert 。

2.音符推出判定区域,即为自动 miss。定义为 miss。

3.玩家点击。定义为 tap。

对于 insert 事件,我们直接将音符压入线段树中。

对于 miss 事件,我们开一个布尔数组表示按键是否被判定过,直接修改布尔数组对应位置即可。

对于 tap 事件,我们根据点击位置遍历线段树对应位置,将有可能被判定的按键压入一个栈中,再利用先前定义下标越小,时间越小的性质,根据下标排序,处理栈中时间相同的前几项即可。

下面给出关于事件的处理代码。

音符进入判定区域。

void insert(int id,int nowl,int nowr,int tol,int tor,int UID){
    if(tol<=nowl&&nowr<=tor){push(id,UID);return;}
    if(tol>nowr||tor<nowl)return;
    int mid=(nowl+nowr)>>1;
    insert(id<<1  ,nowl , mid,tol,tor,UID);
    insert(id<<1|1,mid+1,nowr,tol,tor,UID);
    return;
}

if(now.op==1){
    if(js[now.keyid])continue;
    insert(1,1,tot,key[now.keyid].a,key[now.keyid].b,now.keyid);
    cerr<<"insert\n";
}

音符自动判定为 miss。

else if(now.op==2){
  if(js[now.keyid])continue;
  maxcombo=max(maxcombo,combo);
  combo=0;js[now.keyid]=1;
  miss++;
  cerr<<"miss\n";
}

将可能结算的音符压入栈中。

void update(int id,int nowl,int nowr,int place,int t){
    if(nowr<place||nowl>place)return;
    bool Vis=false;int first_t=0;
    for(int i=head[id];i;i=nxt[i]){
        if(!i)break;
        if(js[uid[i]]){
            nxt[pre[i]]=nxt[i];
            pre[nxt[i]]=pre[i];
            if(i==head[id])head[id]=nxt[i];
            if(i==tail[id])tail[id]=pre[i];
            continue;
        }
        if(Vis&&first_t!=key[uid[i]].t)break;
        stk[++top]=uid[i];
        first_t=key[uid[i]].t;
        Vis=1;
        if(i==tail[id])break;
    }
    if(nowl==nowr)return;
    int mid=(nowl+nowr)/2;
    update(id<<1  ,nowl , mid,place,t);
    update(id<<1|1,mid+1,nowr,place,t);
    return;
}

按照 miss,good,perfect 的顺序结算。

else{
    top=0;
    update(1,1,tot,now.place,now.tl);
    sort(stk+1,stk+1+top);
    int stk2[maxn],top2=0;
    for(int i=1;i<=top;++i){
      if(key[stk[i]].t!=key[stk[1]].t)break;
      stk2[++top2]=stk[i];
    }
    for(int i=1;i<=top2;++i){
      if(js[stk2[i]])continue;
      int delta=key[stk2[i]].t-now.tl;
      if(delta>=60000||delta<=-60000){
        maxcombo=max(maxcombo,combo);
        combo=0;js[stk2[i]]=1;
        miss++;
        cerr<<"miss\n";
      }
    }
    for(int i=1;i<=top2;++i){
      if(js[stk2[i]])continue;
      int delta=key[stk2[i]].t-now.tl;
      if((delta>=20000&&delta<60000)||(delta<=-20000&&delta>-60000)){
        cerr<<"good\n";
        js[stk2[i]]=1;
        combo++;
        good++; 
      }
    }
    for(int i=1;i<=top2;++i){
      if(js[stk2[i]])continue;
      int delta=key[stk2[i]].t-now.tl;
      if(-20000<delta&&delta<20000){
        cerr<<"perfect\n";
        combo++;
        js[stk2[i]]=1;
        perfect++;
      }
    }
  }

那么就只剩事件排序的问题了,如图。

考虑到按键进入判定区间与按键自动miss是开区间,我们在排序时若两个事件同时发生应遵循以下规则:

1.事件 miss 优先于事件 tap。

2.事件 tap 优先于事件 insert。

体现在排序中是这样的:

struct event{int op,keyid,place;int tl;};//op=1音符进入判定区间 op=2音符推出判定区间 op=3点击 
queue<event>q;
event events[maxn<<3];int evecnt;
bool Cmp(event drag,event flick){
    if(drag.tl==flick.tl){
        if(drag.op<=2&&flick.op<=2)return 1;
        if(drag.op==3&&flick.op==3)return 1;
        if(drag.op==1&&flick.op==3)return 0;
        if(drag.op==2&&flick.op==3)return 1;
        if(drag.op==3&&flick.op==1)return 1;
        if(drag.op==3&&flick.op==2)return 0; 
    }
    return drag.tl<flick.tl;
}

总代码如下:

#include<bits/stdc++.h>
#define maxn 114514+10
using namespace std;
int n,m,tot;double tong[maxn<<4];
bool js[maxn];double dub;
int combo,maxcombo,perfect,good,miss;
struct Key{int t;double aa,bb;int a,b;}key[maxn<<1];
struct Tap{int t;double xx;int x;}tap[maxn<<1];
inline bool cmp(Key drag,Key flick){return drag.t<flick.t;}
inline bool CMP(Tap drag,Tap flick){return drag.t<flick.t;}
int stk[maxn<<6],top;

int head[maxn<<6],tail[maxn<<6],chaintot;
int nxt[maxn<<7],uid[maxn<<7],pre[maxn<<7];
void push(int st,int u){
    if(!head[st])head[st]=tail[st]=++chaintot;
    else nxt[tail[st]]=++chaintot,pre[chaintot]=tail[st],tail[st]=chaintot;
    uid[chaintot]=u;return;
}

void insert(int id,int nowl,int nowr,int tol,int tor,int UID){
    if(tol<=nowl&&nowr<=tor){push(id,UID);return;}
    if(tol>nowr||tor<nowl)return;
    int mid=(nowl+nowr)>>1;
    insert(id<<1  ,nowl , mid,tol,tor,UID);
    insert(id<<1|1,mid+1,nowr,tol,tor,UID);
    return;
}

void update(int id,int nowl,int nowr,int place,int t){
    if(nowr<place||nowl>place)return;
    bool Vis=false;int first_t=0;
    for(int i=head[id];i;i=nxt[i]){
        if(!i)break;
        if(js[uid[i]]){
            nxt[pre[i]]=nxt[i];
            pre[nxt[i]]=pre[i];
            if(i==head[id])head[id]=nxt[i];
            if(i==tail[id])tail[id]=pre[i];
            continue;
        }
        if(Vis&&first_t!=key[uid[i]].t)break;
        stk[++top]=uid[i];
        first_t=key[uid[i]].t;
        Vis=1;
        if(i==tail[id])break;
    }
    if(nowl==nowr)return;
    int mid=(nowl+nowr)/2;
    update(id<<1  ,nowl , mid,place,t);
    update(id<<1|1,mid+1,nowr,place,t);
    return;
}
struct event{int op,keyid,place;int tl;};//op=1音符进入判定区间 op=2音符推出判定区间 op=3点击 
queue<event>q;
event events[maxn<<3];int evecnt;
bool Cmp(event drag,event flick){
    if(drag.tl==flick.tl){
        if(drag.op<=2&&flick.op<=2)return 1;
        if(drag.op==3&&flick.op==3)return 1;
        if(drag.op==1&&flick.op==3)return 0;
        if(drag.op==2&&flick.op==3)return 1;
        if(drag.op==3&&flick.op==1)return 1;
        if(drag.op==3&&flick.op==2)return 0; 
    }
    return drag.tl<flick.tl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>dub>>key[i].aa>>key[i].bb;
        key[i].t=dub*1e5+0.5;//映射 
    }
    for(int i=1;i<=n;++i){
        tong[++tot]=key[i].aa;
        tong[++tot]=key[i].bb;
    }
    for(int i=1;i<=m;++i){
        cin>>dub>>tap[i].xx;
        tap[i].t=dub*1e5+0.5;
        tong[++tot]=tap[i].xx;
    }
    sort(tong+1,tong+1+tot);tot=unique(tong+1,tong+1+tot)-(tong+1);
    for(int i=1;i<=n;++i){
        key[i].a=lower_bound(tong+1,tong+1+tot,key[i].aa)-(tong);
        key[i].b=lower_bound(tong+1,tong+1+tot,key[i].bb)-(tong);
    }
    for(int i=1;i<=m;++i)tap[i].x=lower_bound(tong+1,tong+1+tot,tap[i].xx)-(tong);
    //离散化
    sort(key+1,key+1+n,cmp);
    sort(tap+1,tap+1+m,CMP);
    //排序 
    for(int i=1;i<=n;++i){
        events[++evecnt]={1,i,114514,key[i].t-100000};
        events[++evecnt]={2,i,114514,key[i].t+60000};
    }
    cerr<<"track completed\n";
    //导入音符进入事件 
    for(int i=1;i<=m;++i)events[++evecnt]={3,114514,tap[i].x,tap[i].t};
    cerr<<"player completed\n";
    //导入点击时间 
    sort(events+1,events+1+evecnt,Cmp);
    for(int i=1;i<=evecnt;++i)q.push(events[i]);
    //排序事件并压入队列 
    //计算
    while(q.size()){
        event now=q.front();q.pop();
        if(now.op==1){
            if(js[now.keyid])continue;
            insert(1,1,tot,key[now.keyid].a,key[now.keyid].b,now.keyid);
            cerr<<"insert\n";
        }
        else if(now.op==2){
            if(js[now.keyid])continue;
            maxcombo=max(maxcombo,combo);
            combo=0;js[now.keyid]=1;
            miss++;
            cerr<<"miss\n";
        }
        else{
            top=0;
            update(1,1,tot,now.place,now.tl);
            sort(stk+1,stk+1+top);
            int stk2[maxn],top2=0;
            for(int i=1;i<=top;++i){
                if(key[stk[i]].t!=key[stk[1]].t)break;
                stk2[++top2]=stk[i];
            }
            for(int i=1;i<=top2;++i){
                if(js[stk2[i]])continue;
                int delta=key[stk2[i]].t-now.tl;
                if(delta>=60000||delta<=-60000){
                    maxcombo=max(maxcombo,combo);
                    combo=0;js[stk2[i]]=1;
                    miss++;
                    cerr<<"miss\n";
                }
            }
            for(int i=1;i<=top2;++i){
                if(js[stk2[i]])continue;
                int delta=key[stk2[i]].t-now.tl;
                if((delta>=20000&&delta<60000)||(delta<=-20000&&delta>-60000)){
                    cerr<<"good\n";
                    js[stk2[i]]=1;
                    combo++;
                    good++;
                }
            }
            for(int i=1;i<=top2;++i){
                if(js[stk2[i]])continue;
                int delta=key[stk2[i]].t-now.tl;
                if(-20000<delta&&delta<20000){
                    cerr<<"perfect\n";
                    combo++;
                    js[stk2[i]]=1;
                    perfect++;
                }
            }
        }
    }
    maxcombo=max(maxcombo,combo);
    combo=0;
    cout<<perfect<<" "<<good<<" "<<miss<<" "<<maxcombo;
    if(perfect==maxcombo&&(good==0&&miss==0))cerr<<"All Perfect\n";
    else if(maxcombo==n)cerr<<"Full Combo\n"; 
    return 0;
}