2720: [Violet 5]列队春游
Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 217 Solved: 154
[ Submit][ Status][ Discuss]
Description
Input
Output
Sample Input
Sample Output
HINT
Source
这道题非常的神奇, 从O(n^3) 到 O(n^2) 到 O(n) 的算法, 虽然都是可过的, 但是优化的思路却让人深思.
讲一下O(n)的算法, 感谢同机房的bfk的讲解.
首先对于一个学生i来说, 他的视野等于前面比他矮的个数加+1.
假设比i矮的有s个, 我们称之为s集合. 那么对于j∈s, 设f[i]为前面期望比i矮的个数,d[j]是j对i的贡献,p[j]是j对i有贡献的概率. 那么d[j] = 1 * p[j]. f[i] = sigma d[j] (j∈s) + 1. 现在来考虑怎么算p[j], 首先p[j] = (j对i有贡献的排列数)/(n个学生的总排列数) . 所谓有贡献, 也就是说j在i之前, 且j和i之间都是s集合里的人. 看起来这种排列很难算, 但是我们可以换一种思路.
我们先忽略掉除s集合里j之外的s-1个人. 此时我们算出j刚好在i前面一位的排列数. 怎么算呢? 现在删去s-1个人, 还剩n-s+1个人. 因为j在刚好前一位, 所以我们假设j和i是一个人, 那么对剩下n-s个人(j和i合并), 随意乱排列的排列数是 (n-s)!. 因为j和i我们看做一个人, 那么此时(n-s)!可以看做n-s+1个人j在i刚好前一位的所有排列数. 然后我们再把删去的s集合里的s-1个人插进这个排列里. 这样j和i之间的人就肯定是s集合里的了. 把s-1个人插回去的排列数是(n-s)! * n! / (n - s + 1)! , 即(n-s)! * A(n, s-1). 那么p[j] = (n-s)! * A(n, s-1) / n!. 简化而得p[j] = 1 / (n - s + 1). 那么那么f[i] = s / (n - s + 1) + 1. 同时假设i高h, 且高为h的有num[h]个, 那么s集合所作出的贡献就是 num[h] * f[i].
由于算和, 不用算具体的f[i], 直接统计ans即可, 代码很简单.
#include<stdio.h>
double ans;
int num[1005], x, n, s;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &x), num[x]++;
for(int i = 1; i <= 1000; ++i){
if(num[i]) ans += (double) (num[i] * s) / (n - s + 1) + double(num[i]);
s += num[i];
}
printf("%0.2lf\n", ans);
}