【模拟退火】【NOI2008】赛程安排

来源:互联网 发布:淘宝在线制作店招 编辑:程序博客网 时间:2024/04/28 23:51
【问题描述】随着奥运的来临,同学们对体育的热情日益高涨。在 NOI2008 来临之际,学校正在策划组织一场乒乓球赛。小 Z 作为一名狂热的乒乓球爱好者,这正是他大展身手的好机会,于是他摩拳擦掌,积极报名参赛。本次乒乓球赛采取淘汰赛制,获胜者晋级。恰好有 n (n 是 2 的整数次幂,不妨设 n = 2k)个同学报名参加,因此第一轮后就会有 2k-1 个同学惨遭淘汰,另外 2k-1个同学晋级下一轮;第二轮后有 2k-2 名同学晋级下一轮,... 依次类推,直到 k轮后决出冠亚军:具体的,每个人都有一个 1~n 的初始编号,其中小 Z 编号为 1,所有同学的编号都不同,他们将被分配到 n 个位置中,然后按照类似下图的赛程进行比赛:

为了吸引更多的同学参加比赛,本次比赛的奖金非常丰厚。在第 i 轮被淘汰的选手将得到奖金 ai 元,而冠军将获得最高奖金 ak+1 元。显然奖金应满足 a1 < a2 < ... < ak+1.在正式比赛前的热身赛中,小 Z 连连败北。经过认真分析之后,他发现主要的失败原因不是他的球技问题,而是赢他的这几个同学在球风上刚好对他构成相克的关系,所以一经交手,他自然败阵。小 Z 思索:如果在正式比赛中能够避开这几位同学,该有多好啊!假设已知选手两两之间交手的胜率,即选手 A 战胜选手 B 的概率为 PA,B (保证 PA,B + PB,A=1)。于是小 Z 希望能够通过确定比赛的对阵形势(重新给每个选手安排位置) 从而能够使得他获得尽可能多的奖金。你能帮助小 Z 安排一个方案,使得他这场比赛期望获得的奖金最高么?【输入格式】这是一道提交答案型试题,所有的输入文件 match*.in 已在相应目录下。输入文件 match*.in 第一行包含一个正整数 n,表示参赛的总人数,数据保证存在非负整数 k,满足 2k = n。接下来 n 行,每行有 n 个 0 到 1 间的实数 Pi,j,, 表示编号为 i 的选手战胜编号为 j 的选手的概率,每个实数精确到小数点后两位。特别注意 Pi,i = 0.00。接下来 k+1 行,每行一个整数分别为晋级各轮不同的奖金, i 行的数为 ai。【输出格式】输出文件 match*.out 包括 n 行,第 i 行的数表示位于第 i 个位置的同学的编号,要求小 Z 的编号一定位于第 1 个位置。【输入样例】40.00 0.70 0.60 0.800.30 0.00 0.60 0.400.40 0.40 0.00 0.700.20 0.60 0.30 0.00123【输出样例】1423【样例说明】第一轮比赛过后,编号为 1 的选手(小 Z)晋级的概率为 80%,编号为 2 的选手晋级的概率为 60%,编号为 3 的选手晋级的概率为 40%,编号为 4 的选手晋级的概率为 20%。第二轮(决赛),编号为 1 的选手(小 Z)前两轮均获胜的概率为 80% *(60%*70% + 40%*60% ) = 52.8%,因此, 小 Z 在第一轮失败的概率 P1=1-0.8=0.2,第一轮胜出但第二轮败北的概率 P2=0.8-0.528=0.272, 获得冠军的概率 P3=0.528。从而,期望奖金为 0.2*1 + (0.8-0.528)*2 + 0.528*3 = 2.328。【如何测试你的输出】我们提供 match_check 这个工具来测试你的输出文件是否可接受。 使用这个工具的测试方法是在终端中使用命令:./match_check 测试数据编号例如:./match_check 10 表示测试你的 match10.out 是否合法。调用这个程序后,match_check将根据你得到的输出文件给出测试的结果,其中包括:非法退出                       未知错误;Format error.                 输出文件格式错误;Not a permutation.            输出文件不是一个1~n的排列;OK.Your answer is xxx.        输出文件可以被接受,xxx为对应的期望奖金。【评分方法】每个测试点单独评分。对于每一个测试点,如果你的输出文件不合法,如文件格式错误、输出解不符合要求等,该测试点得 0 分。否则如果你的输出的期望奖金为 your_ans,参考期望奖金为 our_ans,我们还设有一个用于评分的参数 d,你在该测试点中的得分如下:如果 your_ans > our_ans,得 12 分。如果 your_ans < our_ans*d,得 1 分。否则得分为:

【提示】“数学期望”数学期望是随机变量最基本的数字特征之一。它反映随机变量平均取值的大小,又称期望或均值。它是简单算术平均的一种推广。例如某城市有 10 万个家庭,没有孩子的家庭有 1000 个,有一个孩子的家庭有 9 万个,有两个孩子的家庭有 6000 个,有 3 个孩子的家庭有 3000 个,则该城市中任一个家庭中孩子的数目是一个随机变量,它可取值 0,1,2,3,其中取 0 的概率为 0.01,取 1 的概率为 0.9, 2 的概率为 0.06, 3 的概率为 0.03,它的数学期望为 0×0.01+1×0.9+2×0.06+3×0.03 等于 1.11,即此城市一个家庭平均有小孩 1.11 个。本题中期望值的计算:假设小 Z 在第一轮被打败的概率为 P1,第一轮胜利且在第二轮被打败的概率为 P2, 前两轮胜利且在第三轮被打败的概率为 P3......,那么小 Z 的期望奖金为:P1 * a1 + P2 * a2 + ...+ Pk+1 * ak+1【特别提示】请妥善保存输入文件*.in 和你的输出*.out,及时备份,以免误删。
大多数数据可以用模拟退火过掉,只是第6、7组数据为特殊数据,需要找规律(未解决)。
模拟退火每次对当前状态估价时,可以调用check程序来计算,调用格式是system("<bash命令>")。

还有就是可以每次先读入上一次的最优解,然后在此基础上继续计算,通过多次退火的方式来求出近似全局最优解。


模拟退火代码:
/**************************\ * @prob: NOI2008 match   * * @auth: Wang Junji      * * @stat: 84分            * * @date: May. 22nd, 2012 * * @memo: 模拟退火         *\**************************/#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <string>#include <ctime>#include <cmath>const int maxN = 510, maxK = 30;double a[maxK], win[maxN][maxN], Max;double p[maxN][maxK], E[maxN][maxK];int ord[maxN], best[maxN], n, m, K, Now;char strin[20], strout[20];inline double _rand() {return (double)rand() / RAND_MAX;}inline double calc(){    double ans = 0;    FILE *out = fopen(strout, "w");    for (int i = 0; i < n; ++i)        fprintf(out, "%d\n", ord[i] + 1);    fclose(out);    static char ths[20], _a[50];    sprintf(ths, "./match_check %d > Now.out", Now);    system(ths);    FILE *tmp = fopen("Now.out", "r");    fgets(_a, 50, tmp);    _a[strlen(_a) - 1] = 0;    sscanf(_a, "OK. Your answer is %lf", &ans);    fclose(tmp);    return ans;} //调用check程序帮助计算。inline void SAA(){    double Last, init = 4.0e4; //对于8、9组数据,初始温度数量级在1e1左右。//    for (double tem = init; tem > 1e0; tem *= .99998)for (double T = 9; ; ++T)    {double tem = init / log10(1 + T);if (tem < 1e0) break;        for (int k = 0; k < 1; ++k)        {            int x, y;            do x = rand() % (n - 1) + 1, y = rand() % (n - 1) + 1;            while (x == y);            std::swap(ord[x], ord[y]);            double delta = calc() - Last;            if (delta > 0)            {                if ((Last += delta) > Max)                {                    Max = Last;                    for (int i = 1; i < n; ++i) best[i] = ord[i];                    static char _tmp[20];                    sprintf(_tmp, "best%d", Now);                    FILE *tmp = fopen(_tmp, "w");                    fprintf(tmp, "%.6lf\n", Max);                    for (int i = 0; i < n; ++i)                        fprintf(tmp, "%d\n", best[i] + 1);                    fclose(tmp);                }                //k = 0;            }            else if (_rand() < exp(delta / tem)) Last += delta;            else std::swap(ord[x], ord[y]);        }#ifdef Debug        printf("%.6lf\t%.6lf\n", Last, tem);#endif    }    return;}int main(){    Now = 10;    sprintf(strin, "./match%d.in", Now);    sprintf(strout, "./match%d.out", Now);    freopen(strin, "r", stdin);    srand(time(NULL) * 1729);    scanf("%d", &n);    static char _tmp[20];    sprintf(_tmp, "best%d", Now);    FILE *tmp = fopen(_tmp, "r");    fscanf(tmp, "%lf", &Max);    for (int i = 0; i < n; ++i)        fscanf(tmp, "%d", best + i), ord[i] = --best[i];    fclose(tmp);    //for (int i = 0; i < n; ++i) ord[i] = best[i] = i;    //std::random_shuffle(ord + 1, ord + n);    SAA();    freopen(strout, "w", stdout);    for (int i = 0; i < n; ++i)        printf("%d\n", best[i] + 1);    return 0;}

原创粉丝点击