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

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

[ 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);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值