ccf-csp期末预测之最佳阈值满分题解202012-2

题目描述

具体来说,顿顿评估了 m m m位同学上学期的安全指数,其中第 i   ( 1 ≤ i ≤ m ) i\ (1\leq i\leq m) i (1im)位同学的安全指数为 y i y_i yi,是一个 [0,108] 范围内的整数;同时,该同学上学期的挂科情况记作 r e s u l t i ∈ 0 , 1 result_i\in 0,1 resulti0,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=1m(predictθ(y)==resultj)

该公式亦可等价地表述为如下规则:

  1. 最佳阈值仅在 y i y_i yi中选取,即与某位同学的安全指数相同;

  2. 按照该阈值对这 m m m位同学上学期的挂科情况进行预测,预测正确的次数最多(即准确率最高);

  3. 多个阈值均可以达到最高准确率时,选取其中最大的。

输入格式

从标准输入读入数据。

输入的第一行包含一个正整数 m m m

接下来输入 m m m行,其中第 i   ( 1 ≤ i ≤ m ) i\ (1\leq i\leq m) i (1im)行包括用空格分隔的两个整数 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 2m105,因此暴力循环法 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

  1. 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=ki=0k1resulti(2)
  2. y i ≥ y k y_i\geq y_k yiyk i ≥ k i\geq k ik,且 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=kmresulti(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 yiyk i ≥ k i\geq k ik存在漏洞
我的方法是采用哈希存储每个 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=0p(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

算法分析

  1. 排序 O ( n l o g n ) O(nlogn) O(nlogn)
  2. 求前缀和 O ( n ) O(n) O(n)
  3. 记录相同y值的起点 O ( n ) O(n) O(n)
  4. 利用前缀和遍历求最大 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值