CCF CSP 202012-2 期末預測之最佳閾值
202012-2 期末預測之最佳閾值
題目背景
考慮到安全指數是一個較大範圍內的整數、小菜很可能搞不清楚自己是否真的安全,頓頓決定設定一個閾值 θ,以便將安全指數 y 轉化為一個具體的預測結果——“會掛科”或“不會掛科”。
因為安全指數越高表明小菜同學掛科的可能性越低,所以當 y≥θ 時,頓頓會預測小菜這學期很安全、不會掛科;反之若 y<θ,頓頓就會勸誡小菜:“你期末要掛科了,勿謂言之不預也。”
那麼這個閾值該如何設定呢?頓頓準備從過往中尋找答案。
題目描述
具體來說,頓頓評估了 m 位同學上學期的安全指數,其中第 i(1≤i≤m)位同學的安全指數為\(y_i\),是一個 \([0,10^8]\)
相應地,頓頓用 \(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 ) } \]該公式亦可等價地表述為如下規則:
- 最佳閾值僅在 \(y_i\)中選取,即與某位同學的安全指數相同;
- 按照該閾值對這 m 位同學上學期的掛科情況進行預測,預測正確的次數最多(即準確率最高);
- 多個閾值均可以達到最高準確率時,選取其中最大的。
輸入格式
從標準輸入讀入資料。
輸入的第一行包含一個正整數 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;
}