zoukankan      html  css  js  c++  java
  • [CSP-S模拟测试]:抽卡(概率DP)

    题目描述

    水上由岐最近在肝手游,游戏里有一个氪金抽卡的活动。有$n$种卡,每种卡有 3 种颜色。每次抽卡可能什么也抽不到,也可能抽到一张卡。每氪金一次可以连抽 m 次卡,其中前$m−1$次抽到第$i$种卡的概率是$p_i$,什么都抽不到的概率为$1−sum limits_{i=1}^n p_i$,如果抽到了卡则这张卡是每种颜色的概率均为$frac{1}{3}$;最后一次抽到第$i$种卡的概率是$q_i$,什么都抽不到的概率为$1−sum limits_{i=1}^n q_i$,如果抽到了卡则这张卡是每种颜色的概率均为$frac{1}{3}$。她想知道,当每种颜色的每种卡全都抽到时,氪金次数的期望。


    输入格式

    第一行,两个正整数$n,m$。
    第二行,$n$个正整数$hat{p}_i$,表示$p_i=dfrac{hat{p}_i}{100n}$。
    第三行,$n$个正整数$hat{q}_i$,表示$q_i=dfrac{hat{q}_i}{100n}$。
    请注意,$m=1$时也会输入$p_i$,尽管此时$p_i$并不会被用到。


    输出格式

    因为输入的概率均为有理数且分母很小,可以证明这个期望可以表示为一个有理数$frac{x}{y}$,其中$x,y$为互质的正整数且$y otequiv 0(mod 2000000011)$,您只需输出$x imes y^{−1}mod 2000000011$(其中$2000000011$是一个质数,$y^{−1}$是$y$模$2000000011$的逆元)。您只需在计算中的每一步都用乘逆元来代替除法即可得到这个答案。


    样例

    样例输入1:

    1 1
    50
    50

    样例输出1:

    11

    样例输入2:

    9 11
    1 1 1 1 1 1 1 1 1
    5 5 5 5 5 5 5 5 5

    样例输出2:

    1080332495


    数据范围与提示

    样例2解释:

    这个数约等于$700.8844378075970484100784276$。

    数据范围:

    对于所有数据,$1leqslant nleqslant 9,1leqslant mleqslant 64,0<sum hat{p}_i,sum hat{q}_ileqslant 100n$。
    下表中的某些测试点有以下两种特殊性质中的一种或两种,一个测试点满足该性质用$surd$表示,不满足用$ imes$表示。
    •性质一:所有$q_i=p_i$。
    •性质二:所有$p_i$相等,所有$q_i$相等。


    题解

      不同种类的卡之间概率不等,不再等价,但是抽到每种颜色的概率都相等,所以对于每种卡来说不同颜色之间是等价的,只需记录每种颜色当前已经抽出了多少张卡($0sim 3$张),于是可以用一个$n$位的四进制数来记录,状态数为$4^n$。

      考虑$DP$,设$dp[s][k]$表示当前的四进制数状态为$s$,本次氪金已经抽了$k$次,到结束时的期望氪金次数。当$s$已经包含了所有颜色的所有种类的卡时,$dp[s][k]=0$;否则,当$k=m$时,因为一次氪金抽卡的结束后是另一次氪金抽卡的开始,所以$dp[s][m]=dp[s][0]+1$;否则,记$c(s,i)$表示状态$s$中第$i$种卡片出现了多少种颜色,则$k<m−1$时(其中$t_i$表示)

      则状态转移方程为:$dp[s][k]=sum limits_{i=1}^n p_i(frac{c(s,i)}{3}dp[s][k+1]+(1-frac{c(s,i)}{3})dp[t_i][k+1])$

      $k=m−1$时将$p_i$换成$q_i$即可。最后答案为$dp[0][m]$(注意$dp[0][0]$表示的是一次氪金抽卡已经开始,$dp[0][m]$才表示还没有开始)。

      然后你可能会想到高斯消元。

      考虑优化,注意到方程组的特殊性,每个变量只依赖另外一个变量。

      首先,$dp[s][m−1]$可以表示成(其中$a_{m−1},b_{m−1}$是可以求出的常量)
      $dp[s][m-1]=a_{m-1}dp[s][m]+b_{m-1}$
      $dp[s][m-2]$可以表示成(其中$c_{m-2},d_{m-2}$是可以求出的常量)
      $dp[s][m-2]=c_{m-2}dp[s][m-1]+d_{m-2}$
      然后将$dp[s][m−1]$代入$dp[s][m−2]$,可以得到
      $dp[s][m-2]=c{m−2}(a_{m−1}dp[s][m]+b_{m−1})+d_{m−2} \ =c_{m−2}a_{m−1}dp[s][m]+c_{m−2}b_{m−1}+d_{m−2} \ =a_{m−2}dp[s][m−1]+b_{m−2}$
      也即

      
    $egin{cases} a_{m-2}=c_{m-2}a_{m-1} \ b_{m-2}=c_{m-2}b_{m-1}+d_{m-2} end{cases} $
      按$k$从大到小的顺序,可以依次求出$a_k,b_k$,即将$dp[s][k]$全部表示成$dp[s][k]=a_kdp[s][m]+b_k$的形式,最后得到
      $dp[s][0]=a_0dp[s][m]+b_0$
      而
      $dp[s][m]=dp[s][0]+1$
      联立两方程可以解出$dp[s][m]$的值,然后再代入$dp[s][k]=a_kdp[s][k]+b_k$求出所有$dp[s][k]$。

    时间复杂度:$Theta(4^nmn)$。

    期望得分:$100$分。

    实际得分:$100$分。


    代码时刻

    #include<bits/stdc++.h>
    using namespace std;
    long long n,m,pos,inv;
    long long p[12],q[12];
    long long dp[300000][100];
    long long a[100],b[100];
    long long qpow(long long x,long long y)
    {
    	long long res=1;
    	while(y)
    	{
    		if(y&1)res=res*x%2000000011;
    		x=x*x%2000000011;
    		y>>=1;
    	}
    	return res;
    }
    int ask(int x,int y){return (x>>(2*y-2))&3;}
    int change(int x,int y){return (x^(ask(x,y)<<(2*y-2)))|((ask(x,y)+1)<<(2*y-2));}
    int main()
    {
    	scanf("%lld%lld",&n,&m);
    	pos=qpow(100*n,2000000009);
    	inv=qpow(3,2000000009);
    	p[0]=q[0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld",&p[i]);
    		p[i]=p[i]*pos%2000000011;
    		p[0]-=p[i];
    	}
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld",&q[i]);
    		q[i]=q[i]*pos%2000000011;
    		q[0]-=q[i];
    	}
    	p[0]=(p[0]%2000000011+2000000011)%2000000011;
    	q[0]=(q[0]%2000000011+2000000011)%2000000011;
    	for(int i=(1<<(n<<1))-2;i>=0;i--)
    	{
    		memset(a,0,sizeof(a));
    		memset(b,0,sizeof(b));
    		a[m-1]=q[0];
    		for(int k=1;k<=n;k++)
    		{
    			long long flag=inv*ask(i,k)%2000000011;
    			a[m-1]=(a[m-1]+q[k]*flag%2000000011)%2000000011;
    			if(ask(i,k)!=3)b[m-1]=(b[m-1]+dp[change(i,k)][m]*q[k]%2000000011*(2000000012-flag)%2000000011)%2000000011;
    		}
    		for(int j=m-2;j>=0;j--)
    		{
    			a[j]=p[0];
    			for(int k=1;k<=n;k++)
    			{
    				long long flag=inv*ask(i,k)%2000000011;
    				a[j]=(a[j]+p[k]*flag%2000000011)%2000000011;
    				if(ask(i,k)!=3)b[j]=(b[j]+dp[change(i,k)][j+1]*p[k]%2000000011*(2000000012-flag)%2000000011)%2000000011;
    			}
    			b[j]=(b[j+1]*a[j]+b[j])%2000000011;
    			a[j]=a[j]*a[j+1]%2000000011;
    		}
    		dp[i][m]=(b[0]+1)*qpow(2000000012-a[0],2000000009)%2000000011;
    		for(int j=0;j<m;j++)
    			dp[i][j]=(a[j]*dp[i][m]+b[j])%2000000011;
    	}
    	printf("%lld",dp[0][m]);
    	return 0;
    }
    

    rp++

  • 相关阅读:
    使用参数化SQL语句进行模糊查找(转载)
    ASP.NET 数据绑定控件(转)
    C#把datetime类型的日期转化成其他格式方法总结
    asp.net MVC中form提交和控制器接受form提交过来的数据(转)
    图说世界编程语言排行
    Android笔记——Matrix
    设计模式——代理模式
    Android笔记——Handler Runnable与Thread的区别
    Android笔记——AsyncTask介绍
    Eclipse---java项目导入报错更改
  • 原文地址:https://www.cnblogs.com/wzc521/p/11482933.html
Copyright © 2011-2022 走看看