BZOJ2720: [Violet 5]列队春游

n<=300个人,每个人的视野是他前面比他严格矮的人数+1,问所有排列中的期望视野总数,高度<=1000。

首先,期望的线性性质,把总视野分成每个人的视野加起来。枚举每个人看贡献。

方法一:枚举一个人,然后枚举他前面有多少人。注意这里可能有两种情况,一种是他前面没有比他高的人,另一种反之,分开计算。如果s表示比这个人矮的个数,那么第一种是$\sum_{j=0}^s \frac{A_s^j * (n-j+1)! * (j+1)}{n!}$,第二种是$\sum_{j=0}^s \frac{A_s^j*(n-s-1)(挑个比他高的放前面)*(n-j-2)!*(n-j-1)(剩下(n-j-2)人乱排后,下图中一坨扔到(n-j-2)人形成的(n-j-2+1)个空位)*(j+1)}{n!}$。

 1 #include<cstring>
 2 #include<cstdlib>
 3 #include<cstdio>
 4 //#include<assert.h>
 5 #include<algorithm>
 6 #include<iostream>
 7 #include<iomanip>
 8 using namespace std;
 9 
10 int n;
11 #define double long double
12 #define maxn 311
13 #define maxb 1011
14 int a[maxn],bud[maxb];
15 double frac[maxn];
16 void pre(int n) {frac[0]=1; for (int i=1;i<=n;i++) frac[i]=frac[i-1]*i;}
17 double A(int n,int m) {return frac[n]/frac[n-m];}
18 int main()
19 {
20     scanf("%d",&n); pre(n);
21     for (int i=1;i<=n;i++) scanf("%d",&a[i]),bud[a[i]]++;
22     double ans=0;
23     for (int i=0,s=0;i<=1000;i++) if (bud[i])
24     {
25         double tmp=0;
26         for (int j=0;j<=s;j++)
27             tmp+=A(s,j)/frac[n]*frac[n-j-1]*(j+1);
28         for (int j=0,to=min(s,n-2);j<=to;j++)
29             tmp+=(j+1)*A(s,j)/frac[n]*(n-s-1)*frac[n-j-2]*(n-j-1);
30         ans+=bud[i]*tmp;
31         s+=bud[i];
32     }
33     cout<<fixed<<setprecision(2)<<ans<<endl;
34     return 0;
35 }
View Code

 

方法二:可以证明每个人的贡献是$\frac{n+1}{n-s_i+1}$,其中$s_i$是比i矮的人数。

现在就考虑j对i有1个贡献的情形吧,这样想,先把(n-s)个>=i(包括i自己)的人在序列里放好,然后一个比i矮的j插到i前面,剩下的$(s_i-1)$个比i矮的乱排。如图:

 

其他的$(s_i-1)$不管怎么乱排都无法影响j对i产生1个贡献。所以i对答案的贡献是$\frac{s_i*(n-s_i)!*A_{n}^{s_i-1}}{n!}+1(自带的)$,整理下就是$\frac{n+1}{n-s_i+1}$。O(n)搞定。

转载于:https://www.cnblogs.com/Blue233333/p/8166273.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值