【Jan.Challenge】持续时刻变化的线段树(某时刻领先选手的位置)

https://www.codechef.com/JAN16/problems/CYCLRACE

两种方法:[线段树+二分] / [ 队列]

但是我发现那些AC的队列代码都过不了一个样例:

3 10
1 0 1 2
2 100
1 1 2 2
2 100
1 50 3 2
2 100
1 50 3 8
2 100
1 52 2 6
2 100      (答案400)

注意到题目中的限制(每个选手只可能会加速,所以速度只增不减),这就使得我们可以维护不同时间的最大值。把图中所有时间的最大值连起来可以变成一个向上围的半凸包,每次更改一个点的速度后只需要把半凸包中的不再是最大值的点去掉,再加上新的这一点(如果是的话)。vector还有pop_back()功能(和队列很像..)

已改成完美代码:

#include <bits/stdc++.h>  
using namespace std;  
typedef long long ll;
const int N = 1e5 + 5;  
ll dis[N];  
int cur[N], t[N];  
int main() {  
    int n, q;  
    cin>>n>>q;  
    //保存速度变化后当前图中所有最大值发生改变的时间以及变化后的下标(可能在将来) 
    vector <pair<int,int> > v;
    pair<int,int> w=make_pair(-1,-1);
    while(q--){  
        int ty;  
        scanf("%d", &ty);  
        if(ty == 1) {  
            int ti, cy, ne;  
            scanf("%d%d%d", &ti, &cy, &ne);  
            while(!v.empty()) {  
                pair<int,int> p = v.back();  
                int com = p.second;  
                if(com == cy) {  //对每个选手,速度的改变肯定是递增的(题目限制,所以可以维护最大值) 
                    v.pop_back();  
                    continue;  
                }  
                ll di_com = dis[com] + 1LL * cur[com] * (max(p.first, ti) - t[com]); //之前路程+之前一段时间内的速度*时间 
                ll di_cur = dis[cy] + 1LL * cur[cy] * (ti - t[cy]);  //之前路程+之前一段时间内的速度*时间
                di_cur += 1LL * ne * max(0, p.first - ti);  //可能在未来,再算上到未来时改变的速度*时间 
                //当前点在未来某时刻会比当前最大点的值大(但是并不一定不会再次被最大点赶超,但是如果有这样的情况也只会发生一次) 
                if(di_cur>di_com){ 
                	v.pop_back(); 
                	ll too = (di_com - di_cur) / (ne - cur[com]) + 1;  
                    if(1LL * max(p.first, ti) + too <= 1e9&& too>=1){
                    	w=make_pair(max(p.first, ti) + too, com); //w记录再次被赶超的时刻 
                    }
                } 
                else if (di_cur<=di_com&&ne <= cur[com]){ //路程小速度也小,就肯定不会是最大值 
                    break;  
                } 
				else {  
                    ll too = (di_com - di_cur) / (ne - cur[com]) + 1; //计算未来超过的时间点,+1是为了取整 
                    if(1LL * max(p.first, ti) + too <= 1e9)  
                        v.push_back({max(p.first, ti) + too, cy});  
                    break;  
                }  
            }  
            if(v.empty()){ 
                v.push_back({ti, cy});  
            }
            if(w.first!=-1)
            	v.push_back(w);
            w=make_pair(-1,-1);
            dis[cy] = dis[cy] + 1LL * cur[cy] * (ti - t[cy]);  //更新点的信息 
            cur[cy] = ne;  
            t[cy] = ti;  
        }   
        else {  
            int time;  
            scanf("%d", &time);  
            if(v.size() == 0) {
                printf("0\n");  
                continue;  
            }  
            //计算前提:时间必须是当前的最晚,即递增 
            int p = lower_bound(v.begin(), v.end(), make_pair(time, 0)) - v.begin(); //【多参数的结构体用upper_bound-1会出错,lower_bound也必须再判断】 
            if(p == v.size() || v[p].first != time) {  
                p--;  
            }   
            pair<int,int> p1 = v[p];  
            printf("%lld\n",dis[p1.second] + 1LL * cur[p1.second] * (time - t[p1.second])); //知道最大点下标可以算出其当前位置 
        }  
    }  
    return 0;  
}

这是一个非常特殊的线段树。普通的线段树在更新点值之后是不会再变化的,但是这题里更改了点的速度之后,由于还有一个参数【时间】在流逝,每个人的路程秒秒钟在按着速度变化,所以直接求区间最大值是没有意义的,而是要结合着时间来求。由于题目有一个限制【速度只升不降】,所以可以用队列维护这个最大值。

线段树的适应面更广些。。。复杂度:O(q*logn)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int tree[50000 * 5 + 1];
ll Ltime[50003]={0},Ldis[50003]={0},Lspeed[50003]={0},leaf_number[50003];
//last_time  last_dis  last_speed
void build(int p,int l,int r){
    if(l>r) return;
    if(l==r) {tree[p]=l;leaf_number[l]=p;return;}
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    tree[p]=tree[p*2];
}

class event{
public:
    ll time,node,cur;
    event(ll _time,ll _node,ll _cur){
        time=_time;
        node=_node;
        cur=_cur;
    }
    void perform();
};

class compareEventByTime{
public:
    bool operator () (const event &a, const event &b) {return a.time > b.time;}
};

priority_queue< event, vector<event> , compareEventByTime > Q;

void event::perform(){ 
	int com = tree[node];
    Ldis[cur] += (time - Ltime[cur]) * Lspeed[cur];
    Ltime[cur]=time;
    Ldis[com] += (time - Ltime[com]) * Lspeed[com];
    Ltime[com]=time;
    if(Ldis[cur] >= Ldis[com]){
        tree[node] = cur;
        if(node == 1) return;
        com = tree[node/2];
        Ldis[com] += (time - Ltime[com]) * Lspeed[com];
        Ltime[com] = time;
        if(Lspeed[cur] > Lspeed[com]){
            ll dis_differ=time * (Lspeed[cur] - Lspeed[com]) + Ldis[com] - Ldis[cur];
            ll speed_diff = (Lspeed[cur] - Lspeed[com]);
            ll newtime = dis_differ / speed_diff;
            while((Ldis[cur]+(newtime-time)*Lspeed[cur]) < (Ldis[com]+(newtime-time)*Lspeed[com])) newtime++;
            event e(newtime,node/2,cur);
            Q.push(e);
        }
    }
    else if(Lspeed[cur] > Lspeed[com]){
        ll dis_differ=time * (Lspeed[cur] - Lspeed[com]) + Ldis[com] - Ldis[cur];
        ll speed_diff = (Lspeed[cur] - Lspeed[com]);
        ll newtime = dis_differ / speed_diff;
        while((Ldis[cur]+(newtime-time)*Lspeed[cur]) < (Ldis[com]+(newtime-time)*Lspeed[com])) 
			newtime++;
        event e(newtime,node,cur);
        Q.push(e);
    }
}

void query1(){ //【时间 time】,【车手编号cur】和【新速度 newspeed】 
    ll time,cur,newspeed;
    cin>>time>>cur>>newspeed;
    
	//刷新事件队列!!!
    while(!Q.empty() && Q.top().time <= time){
        event e=Q.top();
        e.perform();
        Q.pop();
    }
    int node=leaf_number[cur];
    while(tree[node]==cur && node!=1) node/=2;
    if(tree[node]==cur){
        Ldis[cur] += (time-Ltime[cur]) * Lspeed[cur];
        Ltime[cur]=time;
        Lspeed[cur]=newspeed;
        return;
    }
    int com = tree[node];
    Ldis[cur] += (time-Ltime[cur]) * Lspeed[cur];
    Ltime[cur]=time;
    Lspeed[cur]=newspeed;
    Ldis[com] += (time-Ltime[com]) * Lspeed[com];
    Ltime[com]=time;
    if(Lspeed[cur] > Lspeed[com]){
        ll dis_differ=time * (Lspeed[cur] - Lspeed[com]) + Ldis[com] - Ldis[cur];
        ll speed_diff = (Lspeed[cur] - Lspeed[com]);
        ll newtime = dis_differ / speed_diff;
        while((Ldis[cur]+(newtime-time)*Lspeed[cur]) < (Ldis[com]+(newtime-time)*Lspeed[com])) newtime++;
        event e(newtime,node,cur);
        Q.push(e);
    }
}

void query2(){ // 询问【某时刻】领先的选手的位置 
    ll time;
    cin>>time;
    while(!Q.empty() && Q.top().time <= time){
        event e=Q.top();
        e.perform();
        Q.pop();
    }
    int winner = tree[1];
    Ldis[winner] += (time - Ltime[winner]) * Lspeed[winner];
    Ltime[winner] = time;
    cout<<winner<<" "<<Ldis[winner]<<endl; //此时领先的选手编号和位置 
}

int main(){
    int n,q;
    cin>>n>>q;
    build(1,1,n);
    while(q--){
        int type;
        cin>>type;
        if(type==1) query1();
        else query2();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值