BZOJ2720:列队春游(带有部分分)
BZOJ2720 列队春游(带有部分分)
思路&代码
20pts
爆搜即可。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=70;
int n;
int a[N];
double ans;
bool vis[N];
int p[N];
inline int check(){
int sum=0;
FOR(i,1,n)DOR(j,i-1,0)if(p[j]>p[i]&&~(sum+=i-j))break;
return sum;
}
void dfs(int u){
if(u>n)return ans+=check(),void();
FOR(i,1,n)if(!vis[i])p[u]=a[i],vis[i]=1,dfs(u+1),vis[i]=0;
}
signed main(){
cin>>n;
FOR(i,1,n)cin>>a[i];
p[0]=INF,dfs(1);
FOR(i,1,n)ans/=i;
cout<<fixed<<setprecision(2)<<ans<<endl;
return 0;
}
50pts
注意到条件:
所有的 \(h_i\) 互不相同。
那么变成了一道关于全排列的题目。
我们假设现在总共有 \(n\) 个人排成一队,那我们直接把最高的那个人放到中间,此时队伍的两边就全部无关联了,那么我们就可以记录 DP 数组 \(f_i\) 表示对于一个 \(i\) 个人进行排队的队列,视野总期望是多少,则,得到转移方程:
其中,\(f_{j-1},f_{n-j}\) 分别是左右两边的期望,\(j\) 是该次视野。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=3e2+10;
const double EPS=1e-8;
int n;
double f[N];
double dfs(int len){
if(len<2)return (double)(len>0);
if(f[len]>EPS)return f[len];
double res=0;
FOR(i,1,len)res+=(dfs(i-1)+dfs(len-i)+i);
return f[len]=res/len;
}
signed main(){
cin>>n;
cout<<fixed<<setprecision(2)<<dfs(n)<<endl;
return 0;
}
100pts
(\(cnt_i\) 表示小于 \(a_i\) 的数的个数)
\(O(n^3)\)
我们直接枚举每个人,对于他们自己对期望的贡献进行计算,最后累加,除上总方案数。
在对每个人进行枚举时,我们再枚举在每个位置时的贡献,最后枚举“视野”,也就是单次贡献,就可以清楚明了地算出每种情况的方案数了。
设当前第 \(i\) 个人,在第 \(j\) 个位置,视野是 \(k\),我们就可以分情况讨论:
- \(j+k>n\):方案数为 \(\operatorname{A}_{cnt_i}^{k-1} \cdot (j-1)!\);
- \(j+k \le n\):方案数为 \(\operatorname{A}_{cnt_i}^{k-1} \cdot (n - k - 1)! \cdot (n - cnt_i -1)\)。
那么再乘上 \(k\) 就是答案了。
#include<bits/stdc++.h>
#define L_D long double
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=3e2+10;
int n;
int a[N],cnt[N];
L_D fact[N],sum;
inline L_D A(int n,int m){return fact[n]/fact[n-m];}
signed main(){
cin>>n;fact[0]=1.0;
FOR(i,1,n)cin>>a[i],fact[i]=fact[i-1]*i;
sort(a+1,a+n+1);
FOR(i,1,n)cnt[i]=lower_bound(a+1,a+n+1,a[i])-a-1;
FOR(i,1,n)FOR(j,1,n)FOR(k,1,min(n-j,cnt[i])+1)
sum+=A(cnt[i],k-1)*k*(j+k==n+1?fact[j-1]:(n-cnt[i]-1)*fact[n-k-1]);
sum/=fact[n];
cout<<fixed<<setprecision(2)<<sum<<endl;
return 0;
}
(这种方法较易理解,但数据范围大的话就爆掉了……)
\(O(n^2)\)
我们对上面的式子进行优化,改变枚举方式:设当前第 \(i\) 个人,视野是 \(j+1\) 。
依旧是分类讨论:
- 前面没有比他高的:\(\sum_{j=0}^{cnt_i} \operatorname{A}_{cnt_i}^{j} \cdot (n-j+1)! \cdot (j+1)\);
- 相反:\(\sum_{j=0}^{cnt_i} \operatorname{A}_{cnt_i}^{j} \cdot (n - cnt_i - 1) \cdot (n-j+1) \cdot (j+1) \cdot (n-j-2)!\)。
#include<bits/stdc++.h>
#define D double
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=3e2+10,M=1e3+10;
int n;
int a[N],id[M];
D ans;
signed main(){
cin>>n;
FOR(i,1,n)cin>>a[i];
sort(a+1,a+n+1);
FOR(i,1,n){
D p=1.0;
id[a[i]]=(!id[a[i]]?i:id[a[i]]);
FOR(j,1,n-1)ans+=p*1.0*(n-j+1)/n,p*=1.0*(id[a[i]]-j)/(n-j);
ans+=p/n;
}
cout<<fixed<<setprecision(2)<<ans<<endl;
return 0;
}
(极致压行……有些许转换,自行理解)
\(O(n)\)
我们再继续优化上面的方法,但是主要运用数学知识。
法 1
改变枚举方式:设当前第 \(i\) 个人,求他的总贡献。
那么我们再反过来想:第 \(i\) 个人的总贡献,就是所有人对他的贡献。
我们将所有大于等于 \(a_i\) 的数提出来,独立排列,那么总共有 \((n-cnt_i)!\) 种方法,此时我们把一个小于 \(a_i\) 的数揪出来,放在 \(a_i\) 前面,其余随便放,这个小于 \(a_i\) 的数对 \(a_i\) 的贡献就 至少 是 \(1\)。
所以,总答案就是 \(\frac{cnt_i \cdot (n-cnt_i)! \cdot \operatorname{A}_n^{cnt_i-1}}{n!}+1\),化简可得:\(\frac{n+1}{n-cnt_i+1}\)。
法 2
从期望的定义入手:
设离散型随机变量 \(X\) 的分布律为 $P_{ X = x_k } = p_k,k \in N^* $,若级数 \(\sum_{k=1}^{\infty} |x_k| \cdot p_k < +\infty\),记 \(E_X = \sum_{k=1}^{\infty} x_k \cdot p_k\)。则称 \(E_X\) 为随机变量 \(X\) 的数学期望。
那么在此题中,期望定义式应为:
其中 \(L\) 表示视野。
那么,我们通过我 不太懂的 概率学知识,可以得到:
那么,依旧是枚举:当前是第 \(i\) 个人,求其总贡献。
设除他自己之外,身高不小于他的有 \(k\) 个人,则:
#include<bits/stdc++.h>
#define D double
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=3e2+10,M=1e3+10;
int n,sum;
int a[N],cnt[M];
D ans;
signed main(){
cin>>n;
FOR(i,1,n)cin>>a[i],++cnt[a[i]];
FOR(i,1,M-5)
ans+=1.0*cnt[i]*(n+1)/(n-sum+1),sum+=cnt[i];
cout<<fixed<<setprecision(2)<<ans<<endl;
return 0;
}
其实法 1 与 法 2 在本质上是一样的。
启发
新增知识
思想
求期望,概率相关题目时,不要被 DP 限制住了思维,可以通过数学推导来求解。

浙公网安备 33010602011771号