2021牛客暑期多校训练营2 L题

L题: WeChat Walk

原题链接:https://ac.nowcoder.com/acm/contest/11253/L

题目大意

n ( n ≤ 2 ⋅ 1 0 5 ) n(n\le 2·10^5) n(n2105) 个人,他们的好友关系可以用 n n n 个点 m ( m ≤ 2 ⋅ 1 0 5 ) m(m\le 2·10^5) m(m2105) 条边的无向图表示。
每个人会统计当天所走的微信步数,若一个人今天走了至少 1 1 1 步,且步数严格大于他好友列表中其他人的步数,则他是"走路冠军"。
一天被分为 q q q 个时刻,分别求求每个人是"走路冠军"的时间总和。每一时刻会有一个人 u u u 新走了 w w w 步。
所有人今天走的步数均不会超过 1 0 4 10^4 104

题解

对于一个边较少的点:

我们考虑直接暴力枚举他的朋友。
我们以函数形式表示一个人一天的步数变化(如下图中红线):
在这里插入图片描述
我们对其中每一区间(端点取步数变化时)进行考虑,计算出好友第一次步数超过此时步数的时间,进行比较,即可得到该区间中这个人是"走路冠军"的时间,参考下图(两条蓝线表示好友):
在这里插入图片描述
对于 [ l , r ] [l,r] [l,r]区间,我们在这个人的好友的走路历史中查找(可采用二分,因为走路步数只可能增多或不变,为单调函数)第一次超过 y y y 的位置 x x x ,然后分类讨论:
x < l x<l x<l :说明朋友在之前已经超过这个人当前步数,即在区间 [ l , r ] [l,r] [l,r] 中,这个人没有成为“走路冠军”
l ≤ x ≤ r l\le x\le r lxr :说明朋友在区间 [ l , r ] [l,r] [l,r]中超过这个人当前步数,那么在朋友超过之前这个人就是"走路冠军",时间为 x − l x-l xl
x > r x>r x>r :说明朋友在下一次步数改变后才超过这个人当前步数,即这个人在区间 [ l , r ] [l,r] [l,r] 中,一直是走路冠军,时间为 r − l r-l rl
显然,这样计算的所需时间与每个人的走路次数(步数变化次数),朋友数量有关。
步数变化次数为 q q q ,令朋友数为 s s s ,则时间复杂度为 O ( q ⋅ s ⋅  ⁣ l o g q ) O(q·s·\!_{log}q) O(qslogq)

对于边较多的点(如菊花图与完全图的情况):

上述算法速度太慢,会TLE。
那么,我们用向量 m i mi mi 保存朋友较多者的朋友最早到达各个步数的时间(没有直接到达的步数则从大往小进行赋值,同时对每个元素取其自己和比他大 1 1 1 的元素取较小值(即 m i i = m i n ( m i i , m i i + 1 ) mi_i=min(mi_i,mi_{i+1}) mii=min(mii,mii+1) ))然后每次直接比较 r r r x x x 的大小关系即可。
采用分块,每次遍历步行历史,并且对mi进行初始化,算法复杂度应为 O ( m s ( q + W ) ) O(\frac{m}{s}(q+W)) O(sm(q+W)) (W为常数,表示总共的步数可能性,题目中给出范围 W ≤ 1 0 4 W\le 10^4 W104 (即每人每天总步数不超过 1 0 4 10^4 104 )),该复杂度可以优化大型点计算速度,但是因为有常数 W W W ,对于小型点运算则会使其减慢。

分类点的大型与小型

对于两个不同计算方式的时间复杂度,我们提取系数 s s s m s \frac{m}{s} sm ,将总时间复杂度化作 y = s + m s y=s+\frac{m}{s} y=s+sm 的函数形式,那么显然,当 s = m s=\sqrt{m} s=m 时,函数值最小,即分界点为 2 ⋅ 1 0 5 \sqrt{2·10^5} 2105 时,算法复杂度最小(实际操作时,偏差值不过大即可)

参考代码

注意:pair中前一元素保存步数,后一元素保存时间,若调换会影响二分搜索的判断顺序导致错误答案

#include<bits/stdc++.h>
using namespace std;
vector<vector<pair<int,int>>>t(200001);//记录每个人的步行历史
vector<vector<int>>f(200001);//记录每个人的朋友列表
int n,m,q,x,y,r,ans,spy=400;
int main()
{
    std::ios::sync_with_stdio(false),cin.tie(0);
    cin>>n>>m>>q;
    for(int i=0;i<m;i++){
        cin>>x>>y;
        f[x].push_back(y),f[y].push_back(x);
    }
    for(int i=1;i<=q;i++){
        cin>>x>>y;
        t[x].push_back({y,i});//注意存放顺序,先存放步数,后存放时间
    }
    for(int i=1;i<=n;i++)for(int j=1;j<t[i].size();j++)t[i][j].first+=t[i][j-1].first;//将步行历史转化为前缀和形式
    for(int i=1;i<=n;i++){
        ans=0;
        if(f[i].size()<spy){//判断采用何种方式计算
            for(int j=0;j<t[i].size();j++){
                x=t[i][j].second,y=t[i][j].first,r=q;
                if(j<t[i].size()-1)r=t[i][j+1].second;
                for(auto v:f[i]){
                    auto it=lower_bound(t[v].begin(),t[v].end(),(pair<int,int>){y,0});//二分查找
                    if(it!=t[v].end())r=min(r,(*it).second);
                }
                ans+=max(0,r-x);
            }
        }
        else{
            vector<int>mi(10001,q+1);
            for(auto v:f[i])for(auto x:t[v])mi[x.first]=min(mi[x.first],x.second);//记录朋友的最早到达时间
            for(int s=9999;s>=1;s--){mi[s]=min(mi[s],mi[s+1]);}//补全mi
            for(int j=0;j<t[i].size();j++){
                x=t[i][j].second,y=t[i][j].first,r=q;
                if(j<t[i].size()-1)r=t[i][j+1].second;
                r=min(r,mi[y]);
                ans+=max(0,r-x);
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值