【杭电】“钉耙编程”(9)H-Coins 题解

传送门: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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值