测试35:抽卡

状态设计:成环使首尾相连
为何连成环?
单纯链式地跑,状态是无限的。
但是因为状态是首位相接的,所以可以连成环,在环上可以无限地跑
最终状态是f(0,m),而不是f(0,0).(0,m)没开始,(0,0)已开始。
处理环:1、高斯消元。但是是取模意义下的,而且复杂度太大会T
    2、系数递推。相当于手动高斯了。因为to(i,j)一定比当前多,可以先处理出来,就成了常量。
    而环的问题可以写出a*f0+b=fm,fm=f0+1;手动解。
    可行性在于每个状态转移来的未知量只有一个。
to(i,j)函数:s+(1<<x),而不是((s>>x)+1)<<x,这会消去后面的x位1;
写时仔细想想。写完把函数逐一测一测。

#include<bits/stdc++.h>
#define F(i,a,b) for(rg int i=a;i<=b;++i)
#define rg register 
#define LL long long
#define il inline
#define pf(a) printf("%lld ",a)
#define phn puts("")
using namespace std;
#define int LL
int read();
/*
状态设计:成环使首尾相连
为何连成环?
单纯链式地跑,状态是无限的。
但是因为状态是首位相接的,所以可以连成环,在环上可以无限地跑
最终状态是f(0,m),而不是f(0,0).(0,m)没开始,(0,0)已开始。
处理环:1、高斯消元。但是是取模意义下的,而且复杂度太大会T
    2、系数递推。相当于手动高斯了。因为to(i,j)一定比当前多,可以先处理出来,就成了常量。
    而环的问题可以写出a*f0+b=fm,fm=f0+1;手动解。
    可行性在于每个状态转移来的未知量只有一个。
to(i,j)函数:s+(1<<x),而不是((s>>x)+1)<<x,这会消去后面的x位1;
写时仔细想想。写完把函数逐一测一测。
*/
int n,m;
int p[30],q[30];
int f[420010][70];
const int mod=2000000011,inv3=1333333341;
il int qpow(int x,int k){int s=1;for(;k;k>>=1,x=x*x%mod)if(k&1)s=s*x%mod;return s;}
il int cal(int s,int i){
    return ((s>>((i-1)<<1))&3)*1333333341%mod;
}
il int to(int s,int i){
    return s+(1<<((i-1)<<1));
}
int a[70],b[70];
il int MO(int x){return x<mod?x:x-mod;}
signed main(){
//    freopen("ex_card2.in","r",stdin);
    n=read();m=read();
    p[0]=q[0]=100*n;int bas=qpow(100*n,mod-2);
    F(i,1,n)p[0]-=(p[i]=read()),p[i]=p[i]*bas%mod;
    F(i,1,n)q[0]-=(q[i]=read()),q[i]=q[i]*bas%mod;
    p[0]=p[0]*bas%mod;q[0]=q[0]*bas%mod;
    int mx=(1<<n*2)-1;
    for(rg int s=mx-1;~s;--s){
        F(k,0,m)a[k]=p[0],b[k]=0;
        a[m-1]=q[0];a[m]=0; 
        F(i,1,n){
            a[m-1]=MO(a[m-1]+q[i]*cal(s,i)%mod);
            if(1-cal(s,i))
            b[m-1]=MO(b[m-1]+q[i]*(1-cal(s,i)+mod)%mod*f[to(s,i)][m]%mod);
        }
        for(rg int k=m-2;k>=0;--k){
            F(i,1,n){
                a[k]=MO(a[k]+p[i]*cal(s,i)%mod);
                if(1-cal(s,i))
                b[k]=MO(b[k]+p[i]*(1-cal(s,i)+mod)%mod*f[to(s,i)][k+1]%mod);
            }
            b[k]=MO(a[k]*b[k+1]%mod+b[k]);
            a[k]=a[k]*a[k+1]%mod;
        }
        f[s][m]=(b[0]+1)*qpow((1ll-a[0]+mod)%mod,mod-2)%mod;
        for(rg int k=m-1;k>=0;--k){
            f[s][k]=MO(a[k]*f[s][m]%mod+b[k]);
        }
    }
//    pf(f[6]);
    printf("%lld\n",f[0][m]);
}
il int read(){
    int s=0;char ch;
    while(ch=getchar(),!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return s;
}
/*
g++ 1.cpp -g
time ./a.out
1 1
27 74

2 1
84 84
54 54

1 1
27 74
*/
/*
int pc=q[0],s=1;
        F(j,1,n){
            pc=(pc+q[j]*cal(i,j)%mod)%mod;
            s=(s+(1ll-cal(i,j)+mod)*q[j]%mod*f[to(i,j)]%mod)%mod;        
        }
        f[i]=s*qpow((1ll-pc+mod)%mod,mod-2)%mod;
    //    pf(i);pf(f[i]);phn;
*/
View Code

 

 

题解:

对于一般情况,不同种类的卡之间概率不等,不再等价,但是抽到每种颜
色的概率都相等,所以对于每种卡来说不同颜色之间是等价的,只需记录每种
颜色当前已经抽出了多少张卡(0 ∼ 3 张),于是可以用一个 n 位的四进制数来
记录,状态数为 4 n 。
记 f [s][k] 表示当前的四进制数状态为 s,本次氪金已经抽了 k 次,到结束
时的期望氪金次数。当 s 已经包含了所有颜色的所有种类的卡时,f [s][k] = 0;
否则,当 k = m 时,因为一次氪金抽卡的结束后是另一次氪金抽卡的开始,所
以 f [s][m] = f [s][0] + 1;否则,记 c(s, i) 表示状态 s 中第 i 种卡片出现了多少
种颜色,则 k < m − 1 时(其中 t i 表示)
(
(
)
)
n

c(s, i)
c(s, i)
f [s][k] =
p i
f [s][k + 1] + 1 −
f [t i ][k + 1]
3
3
i=1
k = m − 1 时将 p i 换成 q i 即可。最后答案为 f [0][m](注意 f [0][0] 表示的是一
次氪金抽卡已经开始,f [0][m] 才表示还没有开始)。因为 s 相同时转移会成环,
即 f [s][k] 要用到 f [s][k + 1],而 f [s][m] 要用到 f [s][0],可以用高斯消元对这
m + 1 个变量解方程组,时间复杂度为 Θ(4 n (mn + m 3 ))。

 

 

然后将 f [s][m − 1] 代入 f [s][m − 2],可以得到
f [s][m − 2] = c m−2 (a m−1 f [s][m] + b m−1 ) + d m−2
= c m−2 a m−1 f [s][m] + c m−2 b m−1 + d m−2
= a m−2 f [s][m − 1] + b m−2
也即
{
a m−2 = c m−2 a m−1
b m−2 = c m−2 b m−1 + d m−2
按 k 从大到小的顺序,可以依次求出 a k , b k ,即将 f [s][k] 全部表示成 f [s][k] =
a k f [s][m] + b k 的形式,最后得到
f [s][0] = a 0 f [s][m] + b 0

f [s][m] = f [s][0] + 1
联立两方程可以解出 f [s][m] 的值,然后再代入 f [s][k] = a k f [s][k] + b k 求出所
有 f [s][k]。这样避免了高斯消元,时间复杂度为 Θ(4 n mn)。

转载于:https://www.cnblogs.com/seamtn/p/11464882.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值