抽卡 状压DP+期望DP+系数递推

  这题的从状态定义上就有点特别,考场上没想到怎么设定状态来限制1次克金中m次的抽取。只打了m=1的点,没取模爆0了。加上是20分。

  我的状态定义很简单,但是这个题如果把每个物品拆成三个然后硬转移状态数太多,这样的话一共有27个物品,显然1e9就死了。

  那么考虑为什么要状压,其实是因为不同种物品概率不同,并不等价,所以我们要进行转移是要知道该乘谁的概率,那么对于每种物品的每种颜色来说,这概率都相等,就直接记录个数就行了,最多有3个,正好0 1 2 3是4个状态,一种物品4个状态,4进制刚刚好。状态数26万多可过。(插头DP打下的基础。。。。)

  我的定义是每个物品数状态为s的期望购买次数为$f[s]$,那么就可以直接转移了,$f[s]=\sum \limits_{i=1}^{n} q[i]*(sum/3*f[s][i]+(3-sum(s,i))/3*f[add(s,i)]+1$add为在s状态上把第i个物品数加1的新状态,但是如果已经为3那么新状态不再变化,化简一下转移方程,自己从自己转移过来,自环很好处理,手解方程即可。记录一下系数和常数直接作除就行了。

  然后就考虑怎么拿别的分数,发现我的这个转移它一次购买能抽很多次,然后状态就能从很多很多转移过来,需要列很多很多,而且概率也有很多很多,貌似是dfs才能处理的那种,就是枚举每一次抽到什么。

  这种时候就要考虑原来把dfs搞成DP,现在也得把这个dfs搞成DP,那么需要加一维。

  设$f[s][k]$为已有状态为s,此时本次购买已经抽了k次的期望购买次数,这种求期望次数的DP,一定不能把次数放到状态里,因为这个东西有可能是无限次,概率很小,然后贡献答案。那么转移是很显然的。首先$f[满状态][k]=0$

  $f[s][k]=\sum \limits_{i=1}^{n} p[i]*(sum/3*f[s][k+1]+(3-sum)/3*f[add(s,i)][k+1])$

  当$f[s][m]$进行转移时表明状态s已经对其他次数做了贡献,留下来的要往后接着转移就是$f[s][0]=f[s][m]+1$

  这玩意有后效性,但如果把$f[s]$的转移关系列出来只是一个环,这就好办了。

  我们的方法是:系数递推。这是个套路化的化简方法,应用于单环的方程组线性求解,先体会一下过程。以下省略第一维状态

  设$f[k]=a[k]*f[m]+b[k]$

  然后有$f[k-1]=t*f[k]+d$这些系数显然都是可以求的。

  那么$f[k-1]=t*(a[k]*f[m]+b[k])+d$

  $f[k-1]=t*a[k]*f[m]+t*b[k]+d$

  因为$f[k-1]=a[k-1]*f[m]+b[k-1]$

  然后发现就得到了$a[k-1]=t*a[k]$  $b[k-1]=t*b[k]+d$

  在逆推的情况下系数可以直接得到,

  那么我们这么做的目的就出来了,要得到$f[0]=a[0]*f[m]+b[0]$

  然后$f[0]=f[m]+1$接下来不用交给高斯了,交给手就行了

  推出f[m]以后再用$f[k]=a[k]*f[m]+b[k]$推一遍。

  所以系数递推基本思路就是,如果每个方程都能写成一个单环的递推,设0为起始点,通过一条0到m的边得到一个方程,m绕一圈到0的边通过迭代递推的到m到0的方程,交给手解出来。

  

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=9,M=65,mod=2000000011;
long long f[1<<(N<<1)][M],a[M],b[M],p[N],q[N],pi[N],mac[N*2+1];
inline long long rd()
{
    long long s=0,w=1;
    char cc=getchar();
    for(;cc<'0'||cc>'9';cc=getchar()) if(cc=='-') w=-1;
    for(;cc>='0'&&cc<='9';cc=getchar()) s=(s<<3)+(s<<1)+cc-'0';
    return s*w;
}
inline long long qpow(long long a,long long k)
{
    long long ans=1;
    for(;k;k>>=1,a=1ll*a*a%mod) if(k&1) ans=1ll*a*ans%mod;
    return ans;
}
inline long long inv(long long a) {return qpow(a,mod-2);}
inline int find(int s,int j){return (s>>((j-1)<<1))&3;}
inline int add(int s,int j){if(find(s,j)<3) s+=(1<<((j-1)<<1));return s;}
int main()
{
    long long n=rd(),m=rd(),sq=1,sp=1,inv_3=inv(3);a[m]=1;b[m]=0;
    for(int i=1;i<=n;i++) p[i]=rd()*inv(100*n)%mod,sp=(sp-p[i]+mod)%mod;
    for(int i=1;i<=n;i++) q[i]=rd()*inv(100*n)%mod,sq=(sq-q[i]+mod)%mod;
    for(int s=(1<<(n<<1))-2;s>=0;s--)
    {
        for(int k=m-1;k>=0;k--)
        {
            long long t=(k+1==m)?sq:sp,d=0;
            for(int i=1;i<=n;i++)
            {
                long long pi=(k+1==m)?q[i]:p[i];
                t=(t+pi*find(s,i)%mod*inv_3%mod)%mod;
                d=(d+pi*(3ll-find(s,i)+mod)%mod*inv_3%mod*f[add(s,i)][k+1]%mod)%mod;
            }
            a[k]=t*a[k+1]%mod;b[k]=(t*b[k+1]%mod+d)%mod;
        }
        f[s][m]=(1ll+b[0]+mod)%mod*inv((1ll-a[0]+mod)%mod)%mod;
        for(int k=m-1;k>=0;k--)f[s][k]=(a[k]*f[s][m]%mod+b[k])%mod;
    }
    printf("%lld\n",f[0][m]);
    return 0;
}
/*
g++ -std=c++11 1.cpp -o 1
./1
1 1
50
50
*/
View Code

 

posted @ 2019-09-05 16:58  starsing  阅读(279)  评论(0编辑  收藏  举报