【Codeforces】 CF1864H Asterism Stream

题目链接

CF方向
Luogu方向

题目解法

考虑一个时间复杂度较劣,但好理解一些的做法

首先根据期望的线性性,把问题转化成 ∑ i = 0 n − 1 E ( x = i ) \sum\limits_{i=0}^{n-1} E(x=i) i=0n1E(x=i),这一步比较常用
那么现在就考虑变成数 i i i 的概率,下面我们令 N = n − 1 N=n-1 N=n1

考虑对于每次加操作考虑它的贡献,不难发现贡献一定是 c 1 2 0 + c 2 2 1 + . . . + c L + 1 2 L c_12^0+c_22^1+...+c_{L+1}2^L c120+c221+...+cL+12L 的形式,其中 c i c_i ci 表示在两次乘 2 2 2 操作间的加操作的个数
那么现在的问题就变成了求 c 1 2 0 + c 2 2 1 + . . . + c L + 1 2 L ≤ N c_12^0+c_22^1+...+c_{L+1}2^L\le N c120+c221+...+cL+12LN 的个数,可以发现概率为 1 2 L + ∑ c i \frac{1}{2^{L+\sum c_i}} 2L+ci1
考虑 ≤ N \le N N 的限制不优秀,所以把它转化成 = = = 的限制,可以添加一项 c 0 c_0 c0 c 0 + c 1 2 0 + c 2 2 1 + . . . + c L + 1 2 L = N c_0+c_12^0+c_22^1+...+c_{L+1}2^L=N c0+c120+c221+...+cL+12L=N 的方案数(同时需要乘上 1 2 ∑ i = 1 L + 1 c i \frac{1}{2^{\sum\limits_{i=1}^{L+1}c_i}} 2i=1L+1ci1)的系数,注意 c 0 c_0 c0 不能算在内,因为 c 0 c_0 c0 只是为了凑齐 N N N,没有其他的实际意义
考虑把 c i c_i ci 拆分成 2 2 2 进制的形式,那么就可以进行数位 d p dp dp
f i , j f_{i,j} fi,j 表示到第 i i i 位进位为 j j j 的方案数,然后枚举这一位为 1 1 1 的数的个数 k k k 就可以转移,时间复杂度 O ( l o g 3 n ) O(log^3n) O(log3n)
注意到我们需要预处理 g i , j g_{i,j} gi,j 表示在第 i i i 位和为 j j j 的方案数(注意要成 2 − ? 2^{-?} 2? 的系数),这个直接转移即可,注意特判 c 0 c_0 c0
因为对于每一个 L L L 都需要计算,所以时间复杂度 O ( T l o g 4 n ) O(Tlog^4n) O(Tlog4n)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int LIM=70,P=998244353;
int ivpw[LIM];
int f[LIM][LIM];//考虑完了前i位,当前位得到的进位为j的方案数
int g[LIM][LIM],tmp[LIM][LIM];
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
int qmi(int a,int b){
    int res=1;
    for(;b;b>>=1){
        if(b&1) res=1ll*res*a%P;
        a=1ll*a*a%P;
    }
    return res;
}
inline void inc(int &x,int y){ x+=y;if(x>=P) x-=P;}
int calc(int L,int n){
    if(n<0) return 0;
    int lim=ceil(log2(n))+2;
    memset(f,0,sizeof(f));memset(g,0,sizeof(g));
    for(int i=0;i<=lim;i++){
        int cnt=min(i+1,L+1);
        memset(tmp,0,sizeof(tmp));
        tmp[0][0]=1;
        for(int j=0;j<=cnt;j++) for(int k=0;k<=j;k++) if(tmp[j][k]){
            inc(tmp[j+1][k],tmp[j][k]);
            if(!j) inc(tmp[j+1][k+1],tmp[j][k]);
            else inc(tmp[j+1][k+1],tmp[j][k]*ivpw[i-j+1]%P);
        }
        for(int j=0;j<=cnt+1;j++) g[i][j]=tmp[cnt+1][j];
    }
    //c_0+c_12^0+...+c_L2^{L-1}+c_{L+1}2^L=n
    f[0][0]=1;
    for(int i=0;i<lim;i++) for(int j=0;j<LIM;j++) if(f[i][j]) for(int k=0;k<=L+2;k++){
        if(((j+k)&1)!=(n>>i&1)) continue;
        inc(f[i+1][(j+k)/2],f[i][j]*g[i][k]%P);
    }
    return f[lim][0]*qmi(qmi(2,L),P-2)%P;
}
void work(){
    int n=read(),ans=0;n--;
    for(int i=0;i<60;i++) inc(ans,calc(i,n-(1ll<<i)));
    printf("%lld\n",ans);
}
signed main(){
    ivpw[0]=qmi(2,P-2);
    for(int i=1;i<LIM;i++) ivpw[i]=ivpw[i-1]*ivpw[i-1]%P;
    int T=read();
    while(T--) work();
    fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值