题目描述
具体来说,顿顿评估了 m m m位同学上学期的安全指数,其中第 i ( 1 ≤ i ≤ m ) i\ (1\leq i\leq m) i (1≤i≤m)位同学的安全指数为 y i y_i yi,是一个 [0,108] 范围内的整数;同时,该同学上学期的挂科情况记作 r e s u l t i ∈ 0 , 1 result_i\in 0,1 resulti∈0,1,其中 0 表示挂科、1 表示未挂科。
相应地,顿顿用
p
r
e
d
i
c
t
θ
(
y
)
predict_\theta(y)
predictθ(y) 表示根据阈值
θ
\theta
θ将安全指数
y
y
y转化为的具体预测结果。 如果
p
r
e
d
i
c
t
θ
(
y
j
)
predict_\theta(y_j)
predictθ(yj) 与
r
e
s
u
l
t
j
result_j
resultj相同,则说明阈值为
θ
\theta
θ时顿顿对第
j
j
j位同学是否挂科预测正确;不同则说明预测错误。
p
r
e
d
i
c
t
θ
(
y
)
=
{
0
(
y
<
θ
)
1
(
y
≥
θ
)
predict_\theta(y)=\left\{ \begin{aligned} 0\quad(y<\theta) \\ 1\quad(y\geq\theta) \end{aligned} \right.
predictθ(y)={0(y<θ)1(y≥θ)
最后,顿顿设计了如下公式来计算最佳阈值
θ
∗
\theta^*
θ∗
θ ∗ = max arg max θ ∈ y i ∑ j = 1 m ( p r e d i c t θ ( y ) = = r e s u l t j ) \theta^*=\max\ \argmax_{\theta\in y_i}\sum_{j=1}^m(predict_\theta(y)==result_j) θ∗=max θ∈yiargmaxj=1∑m(predictθ(y)==resultj)
该公式亦可等价地表述为如下规则:
-
最佳阈值仅在 y i y_i yi中选取,即与某位同学的安全指数相同;
-
按照该阈值对这 m m m位同学上学期的挂科情况进行预测,预测正确的次数最多(即准确率最高);
-
多个阈值均可以达到最高准确率时,选取其中最大的。
输入格式
从标准输入读入数据。
输入的第一行包含一个正整数 m m m 。
接下来输入 m m m行,其中第 i ( 1 ≤ i ≤ m ) i\ (1\leq i\leq m) i (1≤i≤m)行包括用空格分隔的两个整数 y i y_i yi和 r e s u l t i result_i resulti,含义如上文所述。
输出格式
输出到标准输出。
输出一个整数,表示最佳阈值 θ ∗ \theta^* θ∗。
题目解析
由于全部测试数据保证
2
≤
m
≤
1
0
5
2\leq m \leq 10^5
2≤m≤105,因此暴力循环法
O
(
n
2
)
O(n^2)
O(n2)会TLE
暴力法这里就不讲了,相信大家都做得出来。
前缀和
我们首先将所有的数据按
y
y
y升序排序,
y
y
y相同的按
0
<
1
0<1
0<1排序
如样例1输入所示
6
0 0
1 0
1 1
3 1
5 1
7 1
然后注意到,我们的阈值
θ
\theta
θ取自所有出现过的
y
y
y,而由公式(1),当
y
i
<
θ
y_i<\theta
yi<θ时,有
p
r
e
d
i
c
t
(
y
i
)
=
0
predict(y_i) = 0
predict(yi)=0,反之
p
r
e
d
i
c
t
(
y
i
)
=
1
predict(y_i) = 1
predict(yi)=1
p
r
e
d
i
c
t
θ
(
y
)
=
{
0
(
y
<
θ
)
1
(
y
≥
θ
)
(1)
predict_\theta(y)=\left\{ \begin{aligned} 0\quad(y<\theta) \\ 1\quad(y\geq\theta) \end{aligned} \right.\tag{1}
predictθ(y)={0(y<θ)1(y≥θ)(1)
因此当我们任意选取
y
k
y_k
yk时
- 当
y
i
<
y
k
y_i<y_k
yi<yk即
i
<
k
i<k
i<k,且
r
e
s
u
l
t
i
result_i
resulti=0时预测正确,也就是说,预测正确的数量=预测为0的数量=总的数量-预测为1的数量
而预测为1的数量可以简单由前面所有的 r e s u l t i result_i resulti累加而得,因此得到公式(2)
c o u n t _ l e f t k = k − ∑ i = 0 k − 1 r e s u l t i (2) count\_left_{k}=k-\sum_{i=0}^{k-1} result_i \tag{2} count_leftk=k−i=0∑k−1resulti(2) - 当
y
i
≥
y
k
y_i\geq y_k
yi≥yk即
i
≥
k
i\geq k
i≥k,且
r
e
s
u
l
t
i
result_i
resulti=1时预测正确,也就是说,预测正确的数量=预测为1的数量
c o u n t _ r i g h t k = ∑ i = k m r e s u l t i (3) count\_right_{k}=\sum_{i=k}^{m} result_i \tag{3} count_rightk=i=k∑mresulti(3)
根据公式(2)(3),可以得到计算正确次数 c o u n t k = c o u n t _ l e f t k + c o u n t _ r i g h t k count_k=count\_left_k+count\_right_k countk=count_leftk+count_rightk
而
∑
i
=
k
m
r
e
s
u
l
t
i
\sum_{i=k}^{m} result_i
∑i=kmresulti这类求和,显然可以通过前缀和预处理的方式得到,(i, k)区间的求和可以由prefix[k]-prefix[i]
得到
但是这样还不够
从样例1可以看出,给出的
y
y
y的值并非出现一次,所以上面的
y
i
≥
y
k
y_i\geq y_k
yi≥yk即
i
≥
k
i\geq k
i≥k存在漏洞
我的方法是采用哈希存储每个
y
y
y第一次出现的位置
p
(
y
)
p(y)
p(y),更新后的公式为
c
o
u
n
t
_
l
e
f
t
k
=
p
(
k
)
−
∑
i
=
0
p
(
k
)
−
1
r
e
s
u
l
t
i
(4)
count\_left_{k}=p(k)-\sum_{i=0}^{p(k)-1} result_i \tag{4}
count_leftk=p(k)−i=0∑p(k)−1resulti(4)
c
o
u
n
t
_
r
i
g
h
t
k
=
∑
i
=
p
(
k
)
m
r
e
s
u
l
t
i
(5)
count\_right_{k}=\sum_{i=p(k)}^{m} result_i \tag{5}
count_rightk=i=p(k)∑mresulti(5)
最后只需遍历k值,求得使
c
o
u
n
t
k
count_k
countk最大的k值对应的y值即可,存在相同
c
o
u
n
t
k
count_k
countk时取较大的y
算法分析
- 排序 O ( n l o g n ) O(nlogn) O(nlogn)
- 求前缀和 O ( n ) O(n) O(n)
- 记录相同y值的起点 O ( n ) O(n) O(n)
- 利用前缀和遍历求最大 c o u n t k count_k countk对应y值 O ( n ) O(n) O(n)
因此整体算法时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
AC代码
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
//同学样例
struct A{
int y;//安全指数
int r;//挂科情况
}a[100001];
//结构体按y升序排序,y相同则按r升序排序
bool cmp(A a1, A a2) {
if(a1.y == a2.y) return a1.r < a2.r;
return a1.y < a2.y;
}
int main() {
int n; cin>>n;
int ret = -1;
int maxx = -1;
for(int i = 0; i < n; i ++) {
cin>>a[i].y>>a[i].r;
}
//排序同学们
sort(a, a+n, cmp);
//前缀和数组
int yes[n+1];
yes[0] = 0;
for(int i = 1; i < n+1; i ++) {
yes[i] = yes[i-1] + a[i-1].r;
}
//记录第一个出现的y位置
map<int, int> m;
for(int i = 0; i < n; i ++) {
if(m.count(a[i].y) == 0) {
m[a[i].y] = i;
}
}
//main function
for(int i = 0; i < n; i ++) {
int y = a[i].y;
//在y前面的0的个数 + 在y后面(包括y)的1的个数
int r = (m[y]-yes[m[y]]) + (yes[n]-yes[m[y]]);
//更新r最大的y
if(r >= maxx) {
ret = y;
maxx = r;
}
}
cout<<ret<<endl;
//system("pause");
return 0;
}