1. 程式人生 > 實用技巧 >CCF CSP 202012-2 期末預測之最佳閾值

CCF CSP 202012-2 期末預測之最佳閾值

202012-2 期末預測之最佳閾值

題目背景

考慮到安全指數是一個較大範圍內的整數、小菜很可能搞不清楚自己是否真的安全,頓頓決定設定一個閾值 θ,以便將安全指數 y 轉化為一個具體的預測結果——“會掛科”或“不會掛科”。

因為安全指數越高表明小菜同學掛科的可能性越低,所以當 y≥θ 時,頓頓會預測小菜這學期很安全、不會掛科;反之若 y<θ,頓頓就會勸誡小菜:“你期末要掛科了,勿謂言之不預也。”

那麼這個閾值該如何設定呢?頓頓準備從過往中尋找答案。

題目描述

具體來說,頓頓評估了 m 位同學上學期的安全指數,其中第 i(1≤i≤m)位同學的安全指數為\(y_i\),是一個 \([0,10^8]\)

範圍內的整數;同時,該同學上學期的掛科情況記作 \(result_i∈0,1\),其中 0 表示掛科、1 表示未掛科。
相應地,頓頓用 \(predict_θ(y)\) 表示根據閾值 θ 將安全指數 y 轉化為的具體預測結果。
如果 $predict_θ(y_j) $與 \(result_j\) 相同,則說明閾值為 θ 時頓頓對第 j 位同學是否掛科預測正確;不同則說明預測錯誤。

\[\mathrm{predict}_{\theta} ( y ) = \left\{ \begin{array}{cc} 0& {(y < \theta)} \\1& {(y \ge \theta)} \end{array} \right. \]

最後,頓頓設計瞭如下公式來計算最佳閾值 θ∗:

\[\theta^* = \max { \mathop{\mathrm{argmax} }\limits_{\theta \in { y_i } } \sum\limits_{j=1}^{m} ( \mathrm{predict}_{\theta} ( y_j ) == result_j ) } \]

該公式亦可等價地表述為如下規則:

  1. 最佳閾值僅在 \(y_i\)中選取,即與某位同學的安全指數相同;
  2. 按照該閾值對這 m 位同學上學期的掛科情況進行預測,預測正確的次數最多(即準確率最高);
  3. 多個閾值均可以達到最高準確率時,選取其中最大的。

輸入格式

從標準輸入讀入資料。

輸入的第一行包含一個正整數 m。

接下來輸入 m 行,其中第 i(1≤i≤m)行包括用空格分隔的兩個整數\(y_i\)\(result_i\),含義如上文所述。

輸出格式

輸出到標準輸出。

輸出一個整數,表示最佳閾值 \(\theta^*\)

樣例1輸入

6
0 0
1 0
1 1
3 1
5 1
7 1

樣例1輸出

3

樣例1解釋

按照規則一,最佳閾值的選取範圍為 0,1,3,5,7。

θ=0 時,預測正確次數為 4;

θ=1 時,預測正確次數為 5;

θ=3 時,預測正確次數為 5;

θ=5 時,預測正確次數為 4;

θ=7 時,預測正確次數為 3。

閾值選取為 1 或 3 時,預測準確率最高;
所以按照規則二,最佳閾值的選取範圍縮小為 1,3。

依規則三,\(\theta^*=max1,3=3\)

樣例2輸入

8
5 1
5 0
5 0
2 1
3 0
4 0
100000000 1
1 0

樣例2輸出

100000000

子任務

70% 的測試資料保證 \(m≤200\)

全部的測試資料保證 \(2≤m≤10^5\)

錯誤程式碼

#include<stdio.h>
int main(){
    int  m,i ,j,max,sum=0;
    long int y[200],thta,sumi=0;
    int  r[200],p=0;
    scanf("%d",&m);
    while(i<m){
        scanf("%ld %d",&y[i],&r[i]);
        i++;
    }
    for (i=0;i<m;i++){
        max=0;
        thta=y[i];
        for(j=0;j<m;j++){
                if(y[j]<thta){
                    p=0;
                }else{
                    p=1;
                }
                if(r[j]==p){
                    max ++;
                }
        }
        if(max>sum||max==sum){
            sum=max;
            sumi=thta;
        }
    }    
    printf("%ld", sumi);
    return 0;
}

參考思路1:字首和

轉載自:https://blog.csdn.net/qq_43464088/article/details/112080044

  • 將時間複雜度從\(O(m^2)\)降到\(O(m)\);

  • 解題思路:先對資料按照安全指數yi進行升序排序,然後求出比yi小的0的個數,比yi大的1的個數;

  • 題目資料規模m為1e5,如果用暴力兩層for迴圈是會超時的,只能通過70%的資料,不能拿滿分;

字首和

字首和是一種重要的預處理,能大大降低查詢的時間複雜度。
最簡單的一道題就是給定 n 個數和 m 次詢問,每次詢問一段區間的和。求一個 O(n + m) 的做法。
用 O(n) 字首和預處理,O(m) 詢問。

for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + a[i];  //O(n)
while(m--)        //O(m)
 {
     int L, R; scanf("%d%d", &L, &R);
     printf("%d\n", sum[R] - sum[L - 1]);
 }
程式碼
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,Max=0,res;
int sum[N]={0};
set<int>st;
pair<int,int>pr[N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int a,b;
		cin>>a>>b;
		pr[i]=make_pair(a,b);
	}
	sort(pr+1,pr+n+1);//1.先排序
	for(int i=1;i<=n;i++)
		sum[i] =sum[i-1]+ pr[i].second;//2.求掛科情況字首和 
	for(int i=1;i<=n;i++)
	{
		int a=pr[i].first;//選取閾值 
		if(st.count(a)) continue;//set去重 
		st.insert(a);
		int yuce1 = sum[n]-sum[i-1];//大於等於閾值時,應統計預測結果中為1的個數 
		int yuce0 = i-1-sum[i-1];//小與閾值時,應統計預測結果中為0的個數 
		int yuce = yuce1+ yuce0;//合計預測正確次數 
		if(yuce >= Max) {
			Max=yuce;
			res=a;
		}
	}
	cout<<res;
	return 0;
}

參考思路2

轉載自:https://blog.csdn.net/qq_38632614/article/details/111934286

#include<iostream>
#include<algorithm>
using namespace std;
 
typedef struct Node{
	int theta;
	int result;
}Node;
 
bool cmp(Node a,Node b){
	return a.theta<b.theta;
}
 
int main(){
	int m;
	Node node[100005];
	int flag0[100005]={0}; //記錄小於每個位置點閾值的result=0的個數 
	int flag1[100005]={0}; //記錄大於等於每個位置點閾值的result=1的個數 
	
	/*--輸入--*/ 
	cin>>m;
	for(int i=0;i<m;i++){
		cin>>node[i].theta>>node[i].result;
	}
	sort(node,node+m,cmp); //輸入後排序 
	
	int i=0,j=1;
	int temp0=0,temp1=0;
	/*--統計小於每個閾值的result=0的個數--*/ 
	while(j<m){
		if(node[j].theta==node[i].theta){
			j++;
			continue;
		}
		int temp=0;
		while(i<j){
			if(node[i].result==0)temp++;
			flag0[i]=temp0;
			i++;
		}
		temp0+=temp;
	}
	while(i<j){
		flag0[i]=temp0;
		i++;
	}
	/*--以上統計小於每個閾值的result=0的個數--*/ 
	/*--以下統計大於等於每個閾值的result=1的個數--*/ 
	for(int i=0;i<m;i++){
		if(node[m-1-i].result==1){
			temp1++;
		}
		flag1[m-1-i]=temp1;
	}
	
	//根據flag0和flag1計算每個閾值的準確個數,輸出最大的對應的閾值 
	int ans=0,num=0;
	for(int i=0;i<m;i++){
		if(flag0[i]+flag1[i]>=num){
			num=flag0[i]+flag1[i];
			ans=node[i].theta;
		}
	}
	cout<<ans;
}