题意 :
- 给出n组,每组包含一个字符(+/-)和一个数值
- 对于任意一个数值x,如果x大于 θ \theta θ且为正,为TP;x大于 θ \theta θ且为负,为FP;x小于 θ \theta θ且为正,为FN;小于 θ \theta θ且为负,为TN
- 现定义 T P R = T P / ( T P + F N ) TPR=TP/(TP+FN) TPR=TP/(TP+FN), F P R = F P / ( T N + F P ) FPR=FP/(TN+FP) FPR=FP/(TN+FP)
- 可以发现TPR即所有字符为正的数值中属于TP的数量,FPR…
- 可以根据取不同 θ \theta θ时TPR和FPR的值画出曲线,要求根据的ROC曲线,求出AUC(曲线下面积)
思路 :
- AUC即取到FPR<=1的所有 θ \theta θ对应的TPR这条曲线下的面积,结合图例(分段常数函数)是多个矩形的面积,由若干段TPR*FPR相加,现在的问题是不知道哪里是分界点
- 发现横纵坐标相乘 T P R ∗ d e l t a F P R = ( T P ∗ F P ) / ( ( T P + F N ) ∗ ( T N + F P ) ) TPR*deltaFPR=(TP*FP)/((TP+FN)*(TN+FP)) TPR∗deltaFPR=(TP∗FP)/((TP+FN)∗(TN+FP)),又发现分母是所有字符正乘所有字符负,也就是说分母永远是常数,因此可以先将分子累加,最后除分母
- 根据FPR的值从小到大(由定义公式推出 θ \theta θ从小到大)cmp函数是从小到大的,若值相同,’+'排在‘-’前,这样得到的一组坐标,它的横纵坐标都是递增的(非严格递增)),这样就可以计算了
- 将+和-的值分别放在两个数组里,进行从小到大的cmp排序
- a.end() - upper_bound(a.begin(), a.end(), b[i]) 返回大于b[i]的有几个
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define debug(a) cout << #a << " = " << a << endl;
#define x first
#define y second
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
struct node
{
char op;
int num;
}a[N];
int n;
bool cmp(node a, node b)
{
if (a.num == b.num) // 如果数值相同则先出现符号为-的
return a.op < b.op;
return a.num < b.num; // 先出现数值小的
}
int main()
{
// ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int _ = 1;
// cin >> _;
while (_ -- )
{
scanf("%d", &n);
getchar(); // 同下
int po = 0, ne = 0; // 代表正负总个数
for (int i = 1; i <= n; i ++ )
{
scanf("%c%d", &a[i].op, &a[i].num);
getchar(); // 换行也是char,scanf不会自动去掉换行
if (a[i].op == '+') po ++ ;
else ne ++ ;
}
sort(a + 1, a + n + 1, cmp);
double ans = 0, t = 1; // 乘积和 & TP*FP(>theta的正负乘积)
for (int i = 1; i <= n; i ++ )
{
if (a[i].op == '-') ans += t / ne; // 如果符号是'-',说明出现转折,乘积和(面积和)加上那部分
else t = t - 1.0 / po; // 如果符号是'+',
}
printf("%.10lf\n", ans); // 误差小于等于1e-9
}
return 0;
}
题解二 :
- 当theta变大时,tpr和fpr同时变大,注意到auc函数单调性(当r较大时 我们让FPR尽量大 这样可以让θ尽量大 使得TPR变大),且为若干斜率为0的直线(当r在一段较小的区间内波动时 FPR不会变化 如果让TPR尽量大 就令θ尽量大 此时TPR也不会变化),我们可以分割成一个个的矩形求面积,在遍历的时候将函数分成TN+FP段,每一段长度为1/(tn+fp)
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
//#define FAST ios::sync_with_stdio(false); cin.tie(0);
#define int long long
#define eps 1e-9
using namespace std;
const int MAXN = (int)1e6 + 5;
struct Node {
char op;
int val;
}a[MAXN];
int n, TNFP, TPFN, TP, FP;
bool cmp(Node& a, Node& b) {
if(a.val == b.val) {
if(b.op == '+') return false;
if(a.op == '+') return true;
}
return a.val < b.val;
}
signed main()
{
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i].op >> a[i].val;
if(a[i].op == '+') {
TPFN++;
} else {
TNFP++;
}
}
sort(a + 1, a + 1 + n, cmp);
TP = TPFN, FP = 0;
int tot = 0;
for(int i = 1; i <= n; i++) {
if(a[i].op == '+') { // 每遇到一个'+',tp就减少一个
TP--;
} else { // 为'-'时遇到转折点,答案累加乘积(tp*fp)
tot += TP;
}
}
long double ans = 1.0 * tot / (TPFN * TNFP);
printf("%.9Lf", ans);
//cout << ans;
return 0;
}