【问题描述】 随着奥运的来临,同学们对体育的热情日益高涨。在 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 个位置。 【输入样例】 4 0.00 0.70 0.60 0.80 0.30 0.00 0.60 0.40 0.40 0.40 0.00 0.70 0.20 0.60 0.30 0.00 1 2 3 【输出样例】 1 4 2 3 【样例说明】 第一轮比赛过后,编号为 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组数据为特殊数据,需要找规律(未解决)。
还有就是可以每次先读入上一次的最优解,然后在此基础上继续计算,通过多次退火的方式来求出近似全局最优解。
/**************************\ * @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; }