清楚姐姐玩翻翻乐[期望dp]

在这里插入图片描述
首先这肯定是个期望dp。
首先明确二点
1.一旦这张牌已经知道数字了,下次翻他肯定是给他配对
2.如果已经知道了两张相同数字的卡片,那么在之后什么时候翻面都不会影响操作次数
故dp状态只需要记录只知道一张的卡片数量和未知的卡片数量。
接下来一个问题就是对于一个状态,他的最优操作是什么
首先凭感觉我们可以知道,当未知的牌数量很多的时候,翻两张比较优秀。因为通过基本的概率论计算我们可以知道翻一张知道点数和一张未知点数的卡片,两者相同的概率只有
1 ( 知道一张的卡片数量 ) × ( 未知卡片数量 ) \frac{1}{(知道一张的卡片数量)\times(未知卡片数量)} (知道一张的卡片数量)×(未知卡片数量)1
是一个非常小的值,如果失败则不如翻两张未知的。
接下来我们手动模拟一下小样例的情况:
首先模拟1张已知1张未知,毫无疑问只需要一次操作。
首先模拟1张已知3张未知,通过简单的计算我们可以得到翻两张未知更加优秀,同时通过计算过程我们可以发现未知牌数增多会增加翻"一张已知一张未知“的操作次数期望。
然后模拟2张已知2张未知,通过计算我们发现第一次操作翻一张已知一张未知更加优秀(在这里你可能会很失望,感觉找不出规律了)。
然后模拟2张已知4张未知,通过简单的计算我们可以得到翻两张未知更加优秀。
最后模拟3张已知3张未知同样的我们第一步得到翻两张未知更加优秀。
好,振奋人心!其余的情况我们可以通过单调性来得到
经过探索我们发现,除了(1,1),(2,2)这两种特殊情况的最优操作比较奇怪以外,另外的状态就直接无脑翻两张未知就可以了。good!那让我们来仔细的分类讨论一下(这是我的草稿纸展示捏)
在这里插入图片描述
一共有四种情况,这里我们记已经知道数字(但是反面)的牌的数量是 k k k(know),不知道数字的牌的数量是 d k dk dk(don’t know)(谁能不夸我英语好)
我们翻了两张未知的各个情况的概率
一、两张未知的牌都是没有出现过的数字但是恰好他们的点数一样
在这里插入图片描述

二、两张未知的牌都是没有出现过的数字但是他们的点数不一样
在这里插入图片描述

三、一张是已经出现过的数字,一张是没有出现过的数字
在这里插入图片描述

四、两张未知的牌都是出现过的数字
在这里插入图片描述
那么转移就很简单啦,我们记录这四个概率为 p 1 , p 2 , p 3 , p 4 p_1,p_2,p_3,p_4 p1,p2,p3,p4
d p k , d k = p 1 × ( 1 + d p k , d k − 2 ) + p 2 × ( 1 + d p k + 2 , d k − 2 ) + p 3 × ( 2 + d p k , d k − 2 ) + p 4 × ( 3 + d p k − 2 , d k − 2 ) dp_{k,dk}=p_1\times (1+dp_{k,dk-2})\\+p_2\times (1+dp_{k+2,dk-2})\\+p_3\times (2+dp_{k,dk-2})\\+p_4\times(3+dp_{k-2,dk-2}) dpk,dk=p1×(1+dpk,dk2)+p2×(1+dpk+2,dk2)+p3×(2+dpk,dk2)+p4×(3+dpk2,dk2)
注意上文讨论过的(1,1)=1和(2,2)=5/2的特殊转移哦

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod=1e9+7;
ll poww(ll a,ll b){
    ll t=1;
    while(b){
        if(b&1)t=t*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return t;
}
void add(int &a,int b){
    a+=b;
    if(a>=mod)a-=mod;
}
void del(int &a,int b){
    a-=b;
    if(a<0)a+=mod;
}
int dp[2505][2505];
int dfs(int k,int dk){
    if(dp[k][dk]!=-1)return dp[k][dk];
    if(k==1&&dk==1)return dp[k][dk]=1;
    if(k==2&&dk==2){
        dp[k][dk]=1ll*5*poww(2,mod-2)%mod;
        return dp[k][dk];
    }
    dp[k][dk]=0;
    int tk,tdk;
    ///两个未知一样
    tk=k,tdk=dk-2;
    if(tk>=0&&tdk>=0&&tdk>=tk&&(tdk-tk)%2==0){
        add(dp[k][dk],1ll*(1+dfs(tk,tdk))%mod*(dk-k)%mod*poww(dk*(dk-1),mod-2)%mod);
    }
    ///两个未知不一样
    tk=k+2,tdk=dk-2;
    if(tk>=0&&tdk>=0&&tdk>=tk&&(tdk-tk)%2==0){
        add(dp[k][dk],1ll*(1+dfs(tk,tdk))%mod*(dk-k)%mod*(dk-k-2)%mod*poww(dk*(dk-1),mod-2)%mod);
    }
    ///一个未知
    tk=k,tdk=dk-2;
    if(tk>=0&&tdk>=0&&tdk>=tk&&(tdk-tk)%2==0){
        add(dp[k][dk],1ll*(2+dfs(tk,tdk))%mod*(dk-k)%mod*2%mod*k%mod*poww(dk*(dk-1),mod-2)%mod);
    }
    ///两个已知
    tk=k-2,tdk=dk-2;
    if(tk>=0&&tdk>=0&&tdk>=tk&&(tdk-tk)%2==0){
        add(dp[k][dk],1ll*(3+dfs(tk,tdk))%mod*k%mod*(k-1)%mod*poww(dk*(dk-1),mod-2)%mod);

    }
    //printf("%d %d %d\n",k,dk,dp[k][dk]);
    return dp[k][dk];
}
int main()
{
    //init(200000);
    memset(dp,-1,sizeof(dp));
    int n,m;
    scanf("%d%d",&n,&m);
    unordered_map<int,int>mp;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int t;
            scanf("%d",&t);
            if(t!=0)mp[t]++;
        }
    }
    int cnt1=0,cnt2=0;
    for(auto [fi,se]:mp){
        if(se==1)cnt1++;
        else cnt2++;
    }
    printf("%d",(cnt2+dfs(cnt1,n*m-cnt2*2-cnt1))%mod);
    return 0;
}
/*
5 2
0 0 1 0 0
*/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值