[luogu p7961] [NOIP2021] 数列

Link\mathtt{Link}

P7961 [NOIP2021] 数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Description\mathtt{Description}

给定整数 n,m,kn, m, k,和一个长度为 m+1m + 1 的正整数数组 v0,v1,,vmv_0, v_1, \ldots, v_m

对于一个长度为 nn,下标从 11 开始且每个元素均不超过 mm 的非负整数序列 {ai}\{a_i\},我们定义它的权值为 va1×va2××vanv_{a_1} \times v_{a_2} \times \cdots \times v_{a_n}

当这样的序列 {ai}\{a_i\} 满足整数 S=2a1+2a2++2anS = 2^{a_1} + 2^{a_2} + \cdots + 2^{a_n} 的二进制表示中 11 的个数不超过 kk 时,我们认为 {ai}\{a_i\} 是一个合法序列。

计算所有合法序列 {ai}\{a_i\} 的权值和对 998244353998244353 取模的结果。

Data Range & Restrictions\mathtt{Data} \text{ } \mathtt{Range} \text{ } \mathtt{\&} \text{ } \mathtt{Restrictions}

  • 1kn301 \leq k \leq n \leq 30

  • 0m1000 \leq m \leq 100

  • 1vi<9982443531 \leq v_i < 998244353

Solution\mathtt{Solution}

SS 对应地,我把序列的权值用字母 VV 表示。

这题的样例容易给人启发:一个 aa 序列任意重排,无论是它的 VV 还是 SS 都不会发生改变。也就是说 aa 的顺序不会影响权值和合法性。

我们发现,当两个序列 aabb 可以通过重排互相得到,当且仅当每个元素的出现次数均对应相同,换句话说,令 cic_iii 在序列中出现的次数,那么序列 aabbcc 数组是完全相同的。

因此考虑把研究对象从序列 aa 转化为数组 cc,明显他们是多对一的一个关系,也就是说一个序列 aa 对应一个数组 cc,而一个数组 cc 可以对应多个序列 aa(这些序列都是本质相同的,即可互相重排得到)。

为什么会想到转化成 cc 数组?我是通过可以考虑一个序列 aa 重排得到的序列种数,发现会跟 cc 有关而想到的,一会就会提到。

其实,cc 数组是序列 aa 的一种无序化的体现。这也是本题我们第一步要转化的(好像很多题解都忽略了这点……直接想到?)

为了搞定转化,有两个目标:

  • 求出一个数组 cc 可以对应多少个序列 aa,这个值我们记为 TT
  • VVSS 采用数组 cc 表示。

第一个目标的答案:

T=n!i=0mci!T = \dfrac{n!}{\prod \limits _{i=0} ^m c_i!}

简略解释和花絮

第二个目标的答案:

V=i=0mviciS=i=0mci×2iV = \prod \limits _{i = 0} ^ m {v_i} ^ {c_i}\\S = \sum \limits _{i = 0} ^ m c_i \times 2^i

我们知道,所有合法的 cc 数组的 T×VT \times V 的总和就是答案,因此考虑 T×VT \times V 就是一个 cc 数组的贡献。

搞一下发现是 n!×i=0mvicici!n! \times \prod \limits _{i = 0} ^ m \dfrac{{v_i} ^ {c_i}}{ c_i!},发现 n!n! 是常数,因此只考虑 i=0mvicici!\prod \limits _{i = 0} ^ m \dfrac{{v_i} ^ {c_i}}{ c_i!} 作为 cc 的贡献,最后答案乘上 n!n! 即可。我将 i=0mvicici!\prod \limits _{i = 0} ^ m \dfrac{{v_i} ^ {c_i}}{ c_i!} 记为 GG

dfs cc 数组,预处理 0n0 \sim n 的阶乘逆元和 viv_i 的幂,dfs 的同时转移 SSGG,可做到 O((n+mn))\mathcal{O}(\dbinom{n+m}{n})

至于 (n+mn)\dbinom{n+m}{n},它其实是 cc 数组所有可能的种数,具体见这里

我们把原来数据的表格拓展一下:

测试点 nn kk mm cc 数组的种数(时间复杂度) 是否可过
141 \sim 4 =8=8 n\leq n =9=9 2×104\approx2 \times 10^4
575 \sim 7 =30=30 n\leq n =7=7 107\approx 10^7
8108 \sim 10 =30=30 n\leq n =12=12 1010\approx 10^{10}
111311 \sim 13 =30=30 =1=1 =100= 100 2×1028\approx2 \times 10^{28}
141514 \sim 15 =5=5 n\leq n =50=50 3×106\approx 3 \times 10^6
1616 =15=15 n\leq n =100 =100 2×1018\approx2 \times 10^{18}
171817 \sim 18 =30=30 n\leq n =30=30 1017\approx 10^{17}
192019 \sim 20 =30=30 n\leq n =100 =100 2×1028\approx2 \times 10^{28}

观察到,可以通过 99 个测试点,期望得分 4545 分。差评


考虑正解,计数问题要么是 dp 要么是组合数学,接下来应该是往 dp 的方向走了。数据范围可以给我们提示,我们足够大胆地设置 dp 状态。

接下来的突破口很容易发现,就是 SS 中那个 2i2^i。累加 ci×2ic_i \times 2^i 这个式子,其实就是在 SS 的二进制从低到高第 ii 位上加了 cic_i11。(本题解中第 ii 位就是从低到高第 ii 位,最低一位是第 00 位)

紧接着有一个比较头疼的问题,那就是进位。二进制中的每一位只能容下一个 11,剩下的 11 都要进位到前面去。

不过,冷静下来我们思考,会发现进位并不是一种玄学,它是有理有据的:第 ii 位是 11 还是 00,只和当前这一位对应的 cic_i 和第 i1i - 1 位给第 ii 位进的位数 xix_i 有关。再具体来说,第 ii 位的值是 (ci+xi) mod2(c_i + x_i) \bmod 2。那么这一位又会向第 i+1i + 1 位进多少位呢?不难发现 xi+1=xi+ci2x_{i+1} = \lfloor\dfrac{x_i+c_i}{2}\rfloor

到这里可以开始考虑 dp 方程了。怎么设状态?

我们一定是从 c0c_0cmc_m 递推,对应将 SS 从低位到高位填数。那么设 f(i,)f(i, \cdots) 表示对于满足 \cdots 条件的所有 c0ci1c_0 \sim c_{i-1}j=0i1vjcjcj!\prod \limits _{j = 0} ^ {i-1} \dfrac{{v_j} ^ {c_j}}{c_j!} 的总和。(也就是只计算 c0ci1c_0 \sim c_{i-1} 对应的 GG

为什么这里我都用的是 i1i - 1 呢?这是因为我想给边界条件 f(i=0,)f(i = 0, \cdots) 留下空间,而 f(i=1,)f(i = 1, \cdots) 才开始表示填 c0c_0。换句话说,这里的 ii 可以理解为 cc 总共填了多少个数。

那么我们的意图应该是满足什么条件呢?容易发现有两条:

  • i=0mci=n\sum \limits_{i=0}^mc_i=n
  • popcnt(S)k\operatorname{popcnt}(S) \le k

那么考虑多设两维:f(i,j,p)f(i, j, p),表示新增了两个条件:对于满足 c0ci1c_0 \sim c_{i-1} 的和为 jj,并且 SS 的第 00 位到第 i1i - 1 位为 11 的数量为 pp。(其实从大局来看就是多了两个分类)

这里其实是个经典套路:遇到看似棘手的限制,就考虑设成 dp 中新的一维。

然后又考虑到转移 pp 的时候需要来自上一位的进位情况,因此再多设一维 xx

现在就变成:f(i,j,p,x)f(i, j, p, x),表示对于同时满足一下以下三个条件的:

  • 满足 c0ci1c_0 \sim c_{i- 1} 的和为 jj
  • 满足 SS 的第 00 位到第 i1i - 111 的数量为 pp
  • ii 位收到的进位即将是 xx

所有可能的 c0ci1c_0 \sim c_{i - 1} 的贡献和。

上面那个条件都有个“第 ii 位即将收到的进位”了,那么更好想的转移肯定是刷表法。也就是从 f(i,j,p,x)f(i, j, p, x) 向后转移。

下一个状态是什么呢?肯定是 f(i+1,)f(i +1, \cdots) 吧。所以我们考虑枚举 cic_i 的值,假设它是 tt

那么 f(i,j,p,x)f(i, j, p, x) 的下一个状态就是:f(i+1,j+t,(t+x) mod2+p,t+x2)f(i + 1, j + t, (t + x) \bmod 2 + p, \lfloor\dfrac{t+x}{2}\rfloor)

那么就可以写出转移方程了:

f(i+1,j+t,(t+x) mod2+p,t+x2)f(i + 1, j + t, (t + x) \bmod 2 + p, \lfloor\dfrac{t+x}{2}\rfloor) 自增 f(i,j,p,x)×vitt!f(i, j, p, x) \times \dfrac{{v_i}^t}{t!}

边界是 f(0,0,0,0)=1f(0, 0, 0, 0) = 1


接下来考虑如何统计答案。

显然答案对应的 ff 头两维已经确定:f(m+1,n,)f(m + 1, n, \cdots),我们现在还差一个 SS11 的个数的限制。

但我们发现 SS 统计二进制中 11 的位数时是不止统计到第 mm 位的,也就是说:可能到了 mm 位会再往高进位。

但事实上这个细节很好处理。f(m+1,n,p,x)f(m + 1, n, p, x) 中,pp 是第 00 位到第 mm 位的 11 的个数,而 xx 就是第 m+1m +1 位被进的位。m+1m+1 位及以上,所有的 11 肯定都是通过这个 xx 得到的了,因此事实上第 mm 位一直到更高,11 的个数就是 popcnt(x)\operatorname{popcnt}(x)。那么 SS11 的总数就是 p+popcnt(x)p + \operatorname{popcnt}(x)

所以答案就是所有满足 p+popcnt(x)kp + \operatorname{popcnt}(x) \le kf(m+1,n,p,x)f(m +1, n, p, x) 的总和。

不要忘记最后答案还要乘个 n!n!


还有一个细节:f(i,j,p,x)f(i, j, p, x) 头三维枚举到多少是显然的,xx 应该枚举到多少呢?也就是说:第 ii 位领到的进位最大是多少呢?

其实也挺显然的:xj2x \le \lfloor\dfrac{j}{2}\rfloor。原因:在 c0ci1c_0 \sim c_{i-1} 的总和为 jj 的条件下,把 ci1c_{i-1} 赋为 jj,其他都赋为 00,给 cic_i 的进位显然是最大的,为 j2\lfloor\dfrac{j}{2}\rfloor

Time Complexity\mathtt{Time} \text{ } \mathtt{Complexity}

O(n4m)\mathcal{O}(n^4m)

Code\mathtt{Code}

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-09-12 12:55:18 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-09-12 13:33:29
 */
#include <bits/stdc++.h>
#define int long long

inline int read() {
    int x = 0;
    bool flag = true;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            flag = false;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar();
    }
    if(flag)
        return x;
    return ~(x - 1);
}

const int maxn = 35;
const int maxm = 105;
const int maxk = maxn;
const int maxx = maxn >> 1;
const int mod = 998244353;

int v[maxm][maxn];
int f[maxm][maxn][maxk][maxx];
int iac[maxn];

inline int qpow(int p, int q = mod - 2) {
    int ans = 1;
    while (q) {
        if (q & 1)
            (ans *= p) %= mod;
        (p *= p) %= mod;
        q >>= 1;
    }
    return ans;
}

inline int popcnt(int x) {
    int ans = 0;
    while (x) {
        if (x & 1)
            ++ans;
        x >>= 1;
    }
    return ans;
}

signed main() {
    int n = read(), m = read(), k = read();
    
    for (int i = 0; i <= m; ++i) {
        int val = read();
        v[i][1] = val;
        v[i][0] = 1;
        for (int j = 2; j <= n; ++j)
            v[i][j] = (v[i][j - 1] * val) % mod;
    }
    
    int z = 1;
    for (int i = 2; i <= n; ++i)
        (z *= i) %= mod;
    iac[n] = qpow(z);
    for (int i = n - 1; i >= 0; --i)
        iac[i] = (iac[i + 1] * (i + 1)) % mod;
    
    f[0][0][0][0] = 1;
    
    for (int i = 0; i <= m; ++i)
        for (int j = 0; j <= n; ++j)
            for (int p = 0; p <= k; ++p)
                for (int x = 0; x <= (j >> 1); ++x)
                    for (int t = 0; t <= n - j; ++t)
                        (f[i + 1][j + t][p + ((t + x) & 1)][(t + x) >> 1]
                        += f[i][j][p][x] * v[i][t] % mod * iac[t] % mod) %= mod;
    
    int ans = 0;
    for (int p = 0; p <= k; ++p)
        for (int x = 0; x <= (n >> 1); ++x)
            if (p + popcnt(x) <= k)
                (ans += f[m + 1][n][p][x]) %= mod;
    
    printf("%lld\n", ans * z % mod);
    return 0;
}
posted @   dbxxx  阅读(90)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示