7006. 2021.03.11【2021省赛模拟】玩游戏
有\(n\)个人站在一排,每个人抽到一张卡。这些卡是个\(1\)到\(n\)的排列。
一开始每个人都不知道自己和别人的卡。
从前往后,每个人可以选择:
- 翻开自己的卡,并且公布这张卡的数字。
- 和前面在场的人的手上的牌交换并离场,那个人翻开那张卡并公布那张卡的数字。
每个人都绝顶聪明,都希望自己最后得到的牌期望最大。
卡的分配是随机的。问期望下最终留下的人数。
\(k\le 50\)
\(n\le 10^5\)
输出实数。
\(P\)表示前面留下来的数字的集合,\(R\)表示后面的数字的集合。
结论:如果\(maxP< minR\),则不换;否则换。
证明:\(maxP< minR\)时不换显然,以下证\(maxP>minR\)时换。
如果换,收益为\(maxP\)。如果不换,归纳证明:
称关键时刻为下次\(P\)中排位变化,或出现\(maxP'<minR’\)。
以下证明:对于当前的\(maxP\),设它为\(v_x\),到下次关键时刻的\(v_x'\),一定有\(v_x'<v_x\)。
在关键时刻之前,一定都是选择换。如果是\(P\)的排位变化,那么此时抽到的值一定小于\(P\)的次大值。于是\(maxP'\)变成了\(P\)的次大值。此时显然\(v_x'<v_x\)。
如果是出现\(maxP'<minR'\),排位没有变,\(v_x\)一直是\(maxP\)。则如果\(minR\)不变,那么\(maxP>minR\)变化到\(maxP'<minR'\),\(maxP\)是变小了;如果\(minR\)变了,考虑从前推过来,某一刻当前\(maxP\)用来和当前\(minR\)交换,此时满足\(maxP>minR\),则\(maxP\)变小,然后继续往后推。所以也有\(v_x'<v_x\)
综上,到了关键时刻,一定有\(v_x'<v_x\)。
用DYP的话来说,就是:
以某个时刻的\(v_x\)标准,它可能会换到\(v_x\)以上,因为后面还会继续换所以不管它。于是它肯定要换小。
在\(minR\)不改变的时候,无论怎么换都会保持\(maxP>minR\),因为换来的东西都比\(minR\)大。
换小后如果产生了一些显著变化,它可能不是最大了,也有可能把\(minR\)换了下来。(即“关键时刻”)
接下来证明:如果在\(maxP>minR\)时不换,没可能得到比\(maxP\)更大的收益。设当前点为\(v_y\)。
首先下一个关键时刻总是存在的。最后一次一定是关键时刻。
如果开始时\(v_y<maxP\),如果\(v_y\)能得到更大的收益,那么\(P\)中大于\(v_y\)的一定的排位一定要换到\(v_y\)后面(指值变得更小),使\(v_y\)露出来。从\(v_y\)露出来那个时候开始记,根据上面的结论,到达下一个关键时刻的时候,\(v_y\)一定会变小,并且可能换到后面去。重复刚刚的分析,最终\(v_y\)不会变大。
如果开始时\(v_y>maxP\),则到下一次关键时刻的时候,\(v_y\)可能会换到后面去,可能会换掉\(minR\)但由于之前\(minP>minR\)所以一定会换到后面去。然后就和上面一样了。
证毕。
模拟可以验证最终保留在\(P\)中的元素一定都是在原序列中的后缀最小值。
于是\(S_0(n)=\sum_{i=1}^n\frac{1}{n}=H(n)\)(调和级数)
设\(S\)的生成函数为\(F\)。\(F_k(x)=\frac{-\ln(1-x)}{(1-x)^{k+1}}\)。
求导得\((F_k(x))'=\frac{1}{(1-x)^{k+2}}+(k+1)F_{k+1}(x)\)
由于\(F_k(x)[x^n]=\frac{1}{n}(F_{k}(x))'[x^{n-1}]\),得到\(nS_k(n)=(k+1)S_{k+1}(n-1)+\binom{n+k}{k+1}\)。
于是可以从\(S_{k}(n+1)\)推到\(S_{k+1}(n)\)。
最终算\(H(n)\)的时候,\(n\)小的时候暴力算,\(n\)大的时候根据常识有\(H(n)\)约等于\(\ln(n)+\gamma+\frac{1}{2n}\),其中\(\gamma\)为欧拉常数$$。
题解中也提到\(S_k(n)\)约等于\(\frac{n^k}{k!}(H(n)-H(k))\)。不知道是什么东西。
using namespace std;
#include <bits/stdc++.h>
#define ll long long
long double C(ll m,ll n){
long double p=1;
for (ll i=m;i>m-n;--i) p*=i;
for (ll i=1;i<=n;++i) p/=i;
return p;
}
long double S(ll k,ll n){
if (k==0){
long double s=0;
if (n<=1e7)
for (int i=1;i<=n;++i)
s+=1.0/i;
else
s=log(n)+0.5772156649+1.0/(2*n);
return s;
}
return ((n+1)*S(k-1,n+1)-C(n+k,k))/k;
}
int main(){
// freopen("in.txt","r",stdin);
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
cout.unsetf(ios::fixed);
cout.setf(ios::scientific);
cout.precision(9);
ll k,n;
cin>>k>>n;
cout<<S(k,n);
return 0;
}