L题: WeChat Walk
原题链接:https://ac.nowcoder.com/acm/contest/11253/L
题目大意
有
n
(
n
≤
2
⋅
1
0
5
)
n(n\le 2·10^5)
n(n≤2⋅105) 个人,他们的好友关系可以用
n
n
n 个点
m
(
m
≤
2
⋅
1
0
5
)
m(m\le 2·10^5)
m(m≤2⋅105) 条边的无向图表示。
每个人会统计当天所走的微信步数,若一个人今天走了至少
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
l≤x≤r :说明朋友在区间
[
l
,
r
]
[l,r]
[l,r]中超过这个人当前步数,那么在朋友超过之前这个人就是"走路冠军",时间为
x
−
l
x-l
x−l
③
x
>
r
x>r
x>r :说明朋友在下一次步数改变后才超过这个人当前步数,即这个人在区间
[
l
,
r
]
[l,r]
[l,r] 中,一直是走路冠军,时间为
r
−
l
r-l
r−l
显然,这样计算的所需时间与每个人的走路次数(步数变化次数),朋友数量有关。
步数变化次数为
q
q
q ,令朋友数为
s
s
s ,则时间复杂度为
O
(
q
⋅
s
⋅
l
o
g
q
)
O(q·s·\!_{log}q)
O(q⋅s⋅logq)。
对于边较多的点(如菊花图与完全图的情况):
上述算法速度太慢,会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
W≤104 (即每人每天总步数不超过
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} 2⋅105 时,算法复杂度最小(实际操作时,偏差值不过大即可)
参考代码
注意: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;
}