传送门:H-Coins
标签:数学
题目大意
现有n个人,第i个人有ai枚硬币,你可以进行以下操作:每次随机选择两个目前有硬币的人,让第一个人给第二个人一枚硬币,如果操作结束后第一个人没有了硬币,他将会立即离场。你将一直进行该操作直到场上只剩下一个人(即所有硬币都在一个人手里),问操作数的期望是多少?
输入:T组数据,第一行一个正整数n,第二行n个正整数a1,a2,······an(1<=ai<=1e6)。
输出:期望操作次数。
算法分析
- 随机化情景下求期望是一种很经典的题型,我们一般通过打表解决,但是要从数据中看出规律也需要一定的前提结论。为了找到结论,我们不妨把问题简化,思考n=2时的情况。假如现在场上只有两个人,他们分别有x,y枚硬币。此时我们进行操作,必定会选中这两人,其中每个人作为第一人和第二人的概率都是1/2,也就是说操作结束后可能得到[x-1,y+1]或[x+1,y-1]的结果。想到这里我们就会发现这就是dp的转移。
- 先开一个二维的dp数组,两个维度分别代表两人的初始硬币数,dp值代表某人硬币数为0时的期望操作数。考虑从特殊向普遍推广,我们先假设x=y,那么dp[x][x]就是初始情况。对其进行一次操作会使值+1,然后其状态有一半概率会转移到dp[x-1][x+1],也有一半概率会转移到dp[x+1][x-1]。由对称性可知二者等价,故此时转移方程为dp[x][x]=dp[x-1][x+1]+1。
- 再考虑x和y不相等的情况,我们不妨直接从上面的dp[x-1][x+1]往下推,此时操作仍然会得到两种结果,且二者不等价,可写为dp[x-1][x+1]=1/2dp[x-2][x+2]+1/2dp[x][x]+1。再将第一次转移的式子代入可得:dp[x-1][x+1]=1/2dp[x-2][x+2]+1/2dp[x-1][x+1]+1/2+1。化简可得到:dp[x-1][x+1]=dp[x-2][x+2]+3。继续往下推不难得到:从dp[x][x]往下的相邻两个状态的dp值依次相差1、3、5、······、2n-1,是一个等差数列。而对于无法平分硬币的情况(如dp[x][x+1]),其对应的等差数列为2、4、6、······、2n。
- 这样一来我们就得到了当n=2时的期望计算方法:记dp[x][x]和dp[x][x+1]为初态,dp[i][j]为中态,dp[0][2x]和dp[0][2x+1]为终态,那么显然终态dp值为0,再往上递推就能得到中态dp值为两个等差数列的和之差:S初-终–S初-中,求和公式直接用项数*(首项+末项)/2。化简后就会得到一个惊人的结论:dp[i][j]=i*j。这就是本题的最终结论,再打表就会发现,n>2的情况下其期望值为n-1次相邻两数相乘结果之和。最后还要注意两点:1、本题卡了getchar()所以不能用快读;2、最终结果会爆long long所以要开__int128存储答案并使用快写。
代码实现
#include <iostream>
using namespace std;
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
int a[100005],cnt[1000005];
void write(__int128 x){
if(x>=10)write(x/10);
cout<<(char)(x%10+'0');
}
int main(){
long long i,j,n,mmi,k,m,T,p,now,pos;
__int128 x,y,z,ans;
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n;
ans=0LL;
queue<__int128> q,q1;
for(i=1;i<=n;i++){
cin>>j;
cnt[j]++;
}
for(i=1;i<=1e6;i++)
while(cnt[i]){
q.push(i);
cnt[i]--;
}
for(i=0;i<n-1;i++){
if(q1.empty()||((!q.empty())&&q.front()<q1.front())){
x=q.front();
q.pop();
}
else{
x=q1.front();
q1.pop();
}
if(q1.empty()||((!q.empty())&&q.front()<q1.front())){
y=q.front();
q.pop();
}
else{
y=q1.front();
q1.pop();
}
z=x+y;
if(z&1LL){
z/=2;
ans+=x*(2*z-x+1);
}
else{
z/=2;
ans+=x*(2*z-x);
}
q1.push(x+y);
}
write(ans);
cout<<'\n';;
}
return 0;
}