[BZOJ 3330] 分数 三分法+精度优化

来源:互联网 发布:linux tail 100f 编辑:程序博客网 时间:2024/06/02 19:08

题目传送门:【BZOJ 3330】

题目大意:出题最困难的地方在于调控分数,题目必须让选手之间的差异体现出来。而参加考试的选手的能力水平分布严重影响了题目的得分分布,适合一个省份的题目换到另一个省份可能就会变得没有意义。这里面有一个微妙的关系。为了更好地把握题目的难度,小强建立了一个模型,每个选手的实力都是一个 0 到 100 之间的实数。小强可以掌控一个题目的“难度”和“区分度”。一个选手的得分是:

=1001+e

其中 e 为指数函数。理想情况下,N 个选手 ( 3≤N≤20 ) 的理想分数应当形成一个首项为 100,末项为 0 的等差数列(我们把实力最强的选手排在最前面)。我们定义一个题目的分数偏差为:
(ln(100)+(100)ln(100100))

其中,ln 是自然对数。现在,你要计算:对于给定的 N 个选手的实力,分数偏差的最小值是多少?

输入的第一行为两个正整数 N 和 P,表示选手个数以及精度要求。接下来的 N 行,每行包含一个整数表示选手实力,范围为 [ 0 , 100 ]。
输出为一个实数,表示偏差的最小值,取 P 位有效数字,向下取整。

题目分析:
这道题需要让你求出使偏差最小的难度和区分度的大小。根据题目下方的难度-区分度的图表,结合题意,可以发现偏差值与难度-区分度的关系为一个单峰函数。因此我们可以对其进行三分。

由于有两个变量(难度,区分度),所以我们先固定一个变量,对另一个变量进行三分操作。在这里,我们最好先固定难度,先对区分度进行三分,求出当前难度下区分度最优的情况下的偏差值,然后根据偏差值的大小再对难度进行三分(也就是三分套三分的意思)。直接使用此方法即可……

……

……

……

你以为就完了吗?
这么快就完了才怪呢!
以下为博主 BB 时间,如果你也被莫名其妙的 WA 给弄得心烦意乱,可以参考以下内容。(注意仅仅是参考!没遇到的请轻喷)

1.本题玄学卡精度,直接使用 double 很可能在 P = 10 时爆掉,需要使用 long double 才可以。

2.本题输出方式极为蹊跷,由于输出时需要向下取整,同时保留有效位数,所以为了方便需要先将 long double 转化为 long long,之后再一位一位地输出,同时还要特判小数点的位置。

3.特别注意,计算偏差时,当实际分数过小或者接近 100 时,一定要特判!!

4.本题难度和区分度的取值都非常小,所以三分上下界没必要设得太大,难度在 0 - 15 以内,区分度在 0 - 1 以内就可以了。


下面附上代码:

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#include<cmath>using namespace std;const int MX = 23;const long double eps = 1e-9;int n,P;int strength[MX];                                               //实力值 long double diff_lf,diff_rt,diff_lm,diff_rm;                    //难度 long double lf,rt,lm,rm;                                        //区分度 long double d;                                                  //公差,用于求理想分数 bool comp(const int& i,const int& j){    return i > j;}long long mypow(int a,int b){                                   //在浮点数转化为整数时使用     long long r = 1,base = a;    while (b){        if (b & 1) r *= base;        base *= base;        b >>= 1;    }    return r;}inline long double sigma(long double diff,long double dist){    long double sum = 0,idel = 100;    for (int i = 1;i <= n;i++){        long double score = 100 / (1 + exp(diff - dist * strength[i]));        if (score < 1e-12){            sum += (100.0 - idel) * log(100 / (100 - score));        } else if (score >= 100){            sum += (idel * log(100 / score));        } else        sum += (idel * log(100 / score)  +  (100.0 - idel) * log(100 / (100 - score)));        idel -= d;    }    return sum;}void ans(long double val,int p){    long long w = 1;                                            //判断小数点前有多少位     int ups = 0,used = 0;                                       //控制小数点的位置及是否补 0     for (int i = 1;;i++){        if (val / w*1.0 < 1) break;        w *= 10,ups++;    }    long long res = (long long)(val * mypow(10,10 - ups)),highest = 1000000000;    for (int i = 9;i >= 10 - p;i--){        if (i == 9 - ups){            if (i == 9) printf("0");            printf(".");        }        cout<<(res / highest);        res %= highest;        used++;        highest /= 10;    }    for (;used < ups;used++){        printf("0");    }}int main(){    cin>>n>>P;    d = 100.0 / (n - 1) * 1.0;    for (int i = 1;i <= n;i++)        cin>>strength[i];    sort(strength + 1,strength + n + 1,comp);    diff_lf = 0,diff_rt = 15;                                   //设定难度的上下界     diff_lm = diff_lf + (diff_rt - diff_lf) / 3;    diff_rm = diff_rt - (diff_rt - diff_lf) / 3;    long double lf = 0,rt = 1,min_lm,min_rm,lm,rm;              //设定区分度的上下界     while (diff_rt - diff_lf > eps){        diff_lm = diff_lf + (diff_rt - diff_lf) / 3;        diff_rm = diff_rt - (diff_rt - diff_lf) / 3;        lf = 0,rt = 1;                                          //每次三分难度时,都要重置区分度         lm = lf + (rt - lf) / 3;        rm = rt - (rt - lf) / 3;        while (rt - lf > eps){             lm = lf + (rt - lf) / 3;            rm = rt - (rt - lf) / 3;            if (sigma(diff_lm,lm) < sigma(diff_lm,rm))                rt = rm;            else                lf = lm;        }        min_lm = sigma(diff_lm,lm);        lf = 0,rt = 1,        lm = lf + (rt - lf) / 3,        rm = rt - (rt - lf) / 3;        while (rt - lf > eps){            lm = lf + (rt - lf) / 3;            rm = rt - (rt - lf) / 3;            if (sigma(diff_rm,lm) < sigma(diff_rm,rm))                rt = rm;            else                lf = lm;        }        min_rm = sigma(diff_rm,lm);        if (min_lm < min_rm)                                        //根据两个三分点的答案不同来更新难度             diff_rt = diff_rm;        else            diff_lf = diff_lm;    }    ans(sigma(diff_lm,lm),P);    return 0;}/*Some example inputs: 6 688 72 30 15 7 46 1088 86 82 74 70 685 10100 20 15 10 83 8100 50 04 880 60 40 204 10100 100 100 100*/
原创粉丝点击