2022 牛客多校 第四场 C - Easy Counting Problem

题意

给定 0w1 ( w10 ) 每个数至少出现的次数 ( 合起来不超过 50000 ), q ( q300 ) 次询问, 每次要求计算长度为 n ( n107 ) 的只包含 0w1 的数串且满足以上条件的方案数

思路

显然, 对于每个数的出现次数, 以串长作为生成函数的指数, 有

( [xn]f(x) 表示生成函数的第 n 项系数 )

(1)[xn]f(x)=i0+i1++iw1=nci(ni0+c0,i1+c1,,iw1+cw1)(2)=n!i0+i1++iw1=ncit=0w11(it+ct)!

考虑到这里的 其实就是 wfor 循环, 对它进行展化简得到

(3)[xn]f(x)=n!t=0w1[it+ct==n]it=0nct1(it+ct)!(4)[xn]f(x)n!=t=0w1[jt==n]jt=ctn1jt!

上面式子的 [jt==n] 表示累和等于 n 情况下才有贡献, 对每个 it 进行展开, 记 F(x) 表示 [xn]F(x), 并将右边也写作 [xn]g(x) 的形式

(5)[xn]F(x)=[xn]t=0w1gt(x)(6)=[xn]t=0w1jt=ctnxjtjt!

上式表示, 对每个数 tjt 个, 最后生成函数乘起来的 [xn] 项系数即为结果, 那么接下来可以去掉 [xn]

(7)F(x)=t=0w1jt=ctnxjtjt!

显然枚举到 n 实在是太大了, 此时注意到有点像函数 ex 的泰勒展开形式, 那么就有

(8)F(x)=t=0w1(exkt=0ct1xktkt!)(9)=t=0w1(exGt(x))

其中, 记 Gt(x)=kt=0ct1xktkt!, 那么接下来进行展开, 简单记各个生成函数 Gt 乘积的结果为 Hm, 故 F(x) 形式如下

(10)F(x)=ewx+Hw1e(w1)x++H1e+H0

计算各个 Hm(x), 只需要对 Gt(x) 状压乘起来就行, 接下来对于每个项, 有

(11)Ansi=[xn]Hi(x)eix(12)=j=0sizeof(Hi)[xj]Hi(x)[xnj]eix(13)=j=0sizeof(Hi)[xj]Hi(x)jni(ni)!

其中, O(n) 预处理阶乘, 消费空间 40MB , 每个多项式长度不会超过 50000 , 询问 300 次, 一共最多有 10 个多项式, 复杂度在 1.5108, 非常极限, 如果继续使用快速幂, 很显然会超时, 可以预处理出 w 个低 14 位和高 14 位的幂次乘积, 从而 O(1) 得到幂次值

代码

代码中本来想滚动优化幂次值, 结果发现发现直接预处理 14 位幂次就行了, 原来的离线处理询问没有删干净, 但是懒得改了, 所以有离线处理

constexpr int N = 10000010;
constexpr int sq = 4096;

void sol() {
    int w;
    std::cin >> w;
    std::vector<int> c(w);
    for(int i=0;i<w;++i) {
        std::cin >> c[i];
    }

    std::vector<int> fac(N + 1, 1), ifac(N + 1, 1);

    for(int i=2;i<=N;++i) {
        fac[i] = mul(fac[i - 1], i);
    }
    ifac[N] = Poly::power(fac[N], MOD - 2);
    for(int i=N-1;i>=2;--i) {
        ifac[i] = mul(ifac[i + 1], i + 1);
    }

    std::vector<poly> a(1 << w), d(w + 1);
    for(int T = 0; T < w; ++T) {
        int t = 1 << T;
        a[t].resize(c[T]);
        for(int i = 0; i < c[T]; ++i) {
            a[t][i] = add(0, MOD - ifac[i]);
        }
    }

    a[0] = poly(1, 1);
    for(int dp=0;dp<1<<w;++dp) {
        for(int j=0;j<w;++j)
        if((dp & 1 << j) == 0 && a[dp | 1 << j].empty()) {
            a[dp | 1 << j] = Poly::mul(a[dp], a[1 << j]);
        }
    }
    for(int dp=0;dp<1<<w;++dp) {
        int cto = 0;
        for(int tmp = dp; tmp; tmp >>= 1) if(tmp&1) ++cto;
        int ctz = w - cto;
        d[ctz].resize(std::max(a[dp].size(), d[ctz].size()));
        for(int i=0;i<a[dp].size();++i) d[ctz][i] = add(d[ctz][i], a[dp][i]);
        poly().swap(a[dp]);
    }

    int Q;
    std::cin >> Q;
    std::vector<int> q(Q),pos(Q);
    for(int i=0;i<Q;++i) {
        std::cin >> q[i];
    }
    std::iota(pos.begin(), pos.end(), 0);
    std::sort(pos.begin(), pos.end(), [&](int x,int y) {return q[x] > q[y];});

    std::vector<std::vector<std::array<int,2>>> tb(w + 1, std::vector<std::array<int,2>>(sq));
    for(int i=0;i<sq;++i) tb[1][i][0] = tb[1][i][1] = 1;
    for(int t=2;t<=w;++t) {
        int qwq = Poly::power(t, sq);
        tb[t][0][0] = tb[t][0][1] = 1;
        for(int i=1;i<sq;++i) {
            tb[t][i][0] = mul(tb[t][i - 1][0], t);
            tb[t][i][1] = mul(tb[t][i - 1][1], qwq);
        }
    }

    for(int I=0;I<Q;++I) {
        int n = q[pos[I]];
        int res = 0;
 
        if(n < (int)d[0].size()) res = add(res, d[0][n]);
        for(int t=1;t<=w;++t) {
            int m = (int)d[t].size();
            for(int i=0;i<m&&i<=n;++i) {
                res = add(res, mul(mul(ifac[n - i], mul(tb[t][(n - i) % sq][0], tb[t][(n - i) / sq][1])), d[t][i]));
            }
        }

        q[pos[I]] = mul(res, fac[n]);
    }
    for(int e : q) {
        std::cout << e << '\n';
    }
}
posted @   LacLic  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示