[BZOJ] 2720 列队春游 期望DP O(n)

来源:互联网 发布:ecshop 2.0数据字典 编辑:程序博客网 时间:2024/05/18 01:09

2720: [Violet 5]列队春游

Time Limit: 5 Sec  Memory Limit: 128 MB
Submit: 217  Solved: 154
[Submit][Status][Discuss]

Description

Input

Output

Sample Input

Sample Output

HINT

Source

interviewstreet--virtual stick

[Submit][Status][Discuss]


HOME Back

     这道题非常的神奇, 从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);}