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_i = \frac{\sum_{j=1}^{i}(f_{j-1}+f_{i-j}+j)}{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\),我们就可以分情况讨论:

  1. \(j+k>n\):方案数为 \(\operatorname{A}_{cnt_i}^{k-1} \cdot (j-1)!\)
  2. \(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\)

依旧是分类讨论:

  1. 前面没有比他高的:\(\sum_{j=0}^{cnt_i} \operatorname{A}_{cnt_i}^{j} \cdot (n-j+1)! \cdot (j+1)\)
  2. 相反:\(\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\) 的数学期望。

那么在此题中,期望定义式应为:

\[E = \sum_{i=1}^{n} i \cdot P_{L=i} \]

其中 \(L\) 表示视野。

那么,我们通过我 不太懂的 概率学知识,可以得到:

\[E = \sum_{i=1}^{n} P_{L \ge i} \]

那么,依旧是枚举:当前是第 \(i\) 个人,求其总贡献。

设除他自己之外,身高不小于他的有 \(k\) 个人,则:

\[\begin{aligned} E_i & = \sum_{i=1}^n \frac{(n-i+1)\operatorname{A}_{n-i}^{k}}{\operatorname{A}_{n}^{k+1}} \\ E_i & = \sum_{i=1}^n \frac{(n-i+1)\frac{(n-i)!}{(n-i-k)!}}{\frac{n!}{(n-k-1)!}} \\ E_i & = \frac{(n-k-1)!}{n!} \sum_{i=1}^n {\frac{(n-i+1)!}{(n-i-k)!}} \\ E_i & = \frac{(n-k-1)!(k+1)!}{n!} \sum_{i=1}^n {\frac{(n-i+1)!}{(n-i-k)!(k+1)!}} \\ E_i & = \frac{(n-k-1)!(k+1)!}{n!} \sum_{i=1}^n \operatorname{C}_{n-i+1}^{k+1} \\ E_i & = \frac{(n-k-1)!(k+1)!}{n!} \operatorname{C}_{n+1}^{k+2} \\ E_i & = \frac{n+1}{k+2} \\ \end{aligned} \]

#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 在本质上是一样的。


启发

新增知识

\[\begin{aligned} E & = \sum_{i=1}^{n} i \cdot P_{L=i} \\ E & = \sum_{i=1}^{n} P_{L \ge i} \\ \end{aligned} \]

思想

求期望,概率相关题目时,不要被 DP 限制住了思维,可以通过数学推导来求解。


posted @ 2024-05-02 20:51  Add_Catalyst  阅读(85)  评论(0)    收藏  举报