P7961 [NOIP2021] 数列

题目描述

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

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

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

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

输入格式

输入第一行是三个整数 n,m,kn, m, kn,m,k

第二行 m+1m + 1m+1 个整数,分别是 v0,v1,…,vmv_0, v_1, \ldots, v_mv0,v1,,vm

输出格式

仅一行一个整数,表示所有合法序列的权值和对 998244353998244353998244353 取模的结果。

输入输出样例 #1

输入 #1

5 1 1
2 1

输出 #1

40

输入输出样例 #2

输入 #2

8 9 4
934258593 150407625 187068439 162292791 219945760
512449588 803393963 983648121 484675481 412407699

输出 #2

642171527

说明/提示

【样例解释 #1】

由于 k=1k = 1k=1,而且由 n≤S≤n×2mn \leq S \leq n \times 2^mnSn×2m 知道 5≤S≤105 \leq S \leq 105S10,合法的 SSS 只有一种可能:S=8S = 8S=8,这要求 aaa 中必须有 222000333111,于是有 (52)=10\binom{5}{2} = 10(25)=10 种可能的序列,每种序列的贡献都是 v02v13=4v_0^2 v_1^3 = 4v02v13=4,权值和为 10×4=4010 \times 4 = 4010×4=40

【数据范围】

对所有测试点保证 1≤k≤n≤301 \leq k \leq n \leq 301kn300≤m≤1000 \leq m \leq 1000m1001≤vi<9982443531 \leq v_i < 9982443531vi<998244353

测试点 nnn kkk mmm
1∼41 \sim 414 =8=8=8 ≤n\leq nn =9=9=9
5∼75 \sim 757 =30=30=30 ^ =7=7=7
8∼108 \sim 10810 ^ ^ =12=12=12
11∼1311 \sim 131113 ^ =1=1=1 =100=100=100
14∼1514 \sim 151415 =5=5=5 ≤n\leq nn =50=50=50
161616 =15=15=15 ^ =100=100=100
17∼1817 \sim 181718 =30=30=30 ^ =30=30=30
19∼2019 \sim 201920 ^ ^ =100=100=100

首先看到数据范围,n=30n=30n=30m=100m=100m=100,非常小,但如果你觉得这道题很简单,那就完蛋了,首先你必须想到,这是NOIP的T2,CCF肯定不会出一道简单题,那么你就要小心了,因为这很可能是一道高维动态规划(事实也确实如此)。

首先,如果你对于这道题目无从下手,教你一个技巧:先考虑去掉几个限制条件。你认为最难做的约束是什么?显然是SSS在二进制下不超过kkk111。那我们把这个限制去掉会发生什么?那就是说,在000mmm中,有序地选择nnn个,组成数列{an}\{a_n\}{an},计算所有∏i=1nvai\prod_{i=1}^{n} v_{a_i}i=1nvai,那么很明显,这就是一道完全背包问题!状态转移方程很明显,就是:
dp[i][j]=∑u=0j(ju)viu⋅dp[i−1][j−u] dp[i][j] = \sum_{u=0}^{j} \binom{j}{u} v_i^u \cdot dp[i-1][j-u] dp[i][j]=u=0j(uj)viudp[i1][ju]
其中uuu表示选取了uuuiii,而答案就是dp[n][m]dp[n][m]dp[n][m]

现在我们再考虑SSS在二进制下不超过kkk111。由于在上述完全背包中,每次选取的个数是枚举出来的,也就是说是确定的(即为ppp个),那么S′S^\primeS就等于S+p⋅2iS+p\cdot2^iS+p2i,于是状态转移方程:
dp[i][j][p+u⋅2i]=∑u=0j∑p=0(j−u)⋅2i−1(ju)viu⋅dp[i−1][j−u][p] dp[i][j][p+u\cdot2^i] = \sum_{u=0}^{j}\sum_{p=0}^{(j-u)\cdot2^{i-1}} \binom{j}{u} v_i^u \cdot dp[i-1][j-u][p] dp[i][j][p+u2i]=u=0jp=0(ju)2i1(uj)viudp[i1][ju][p]
提前处理每个数在二进制下有几个111,设这个数组为g[u]g[u]g[u],答案就是
∑p=nn⋅2mdp[m][n][p]⋅(g[p]<=k) \sum_{p=n}^{n\cdot2^m}dp[m][n][p] \cdot(g[p]<=k) p=nn2mdp[m][n][p](g[p]<=k)
显然,由于最大m=100m=100m=100,数组不可能开这么大,就算能开这么大也不可能在限制时间内跑完(时间复杂度O(m⋅n2⋅2m)O(m\cdot n^2\cdot2^m)O(mn22m))。我们需要一个优化算法。

再说一个实用的小技巧:根据数据范围确定维度数量。观察到n=30n=30n=30m=100m=100m=100,由此可以肯定动态规划至少是444维的。

怎么进行优化呢?让我们想一想:原来的枚举每个SSS复杂度太高,但每个SSS在二进制下每一位都是需要储存的有效信息吗?显然不是,我们发现:由于我们按照从000mmm的顺序进行枚举,那么对于SSS的影响,必定是以2i2^i2i为单位的,也就是说,iii位以下有多少个111,每个111分别在哪个位置都对后续规划无影响!

那么,我们就已经优化掉2i−12^{i-1}2i1位了,再思考一下:对于每一位,我们最多只能选nnn个,也就是说,SSS变化量最多只有n⋅2in\cdot 2^in2i,而我们已经优化掉了2i−12^{i-1}2i1位,也就是说,SSS已经右移了i−1i-1i1位,那么SSS的变化量最多就只有nnn!但我们依然要储存二进制当前有多少个111,于是还要多开一个维度,表示二进制当前有多少个111

好了,现在我们有四个维度,设为iiijjjpppqqq,表示正在选第iii位,选完之后总共选了jjj个数,SSS的二进制在第iii位及其高位的数为ppp(这样讲比较抽象,实际就是SSS右移i−1i-1i1位所得的数是ppp),这个序列的SSS值中有qqq111,现在我们依次考虑这几个数怎么更新。

显而易见地,对于第一维,用i−1i-1i1去更新iii,对于第二维,用j−uj-uju去更新jjj

剩下两维是重点考虑对象。对于第三维,由于我们只考虑SSS二进制下iii位及以上,考虑第i−1i-1i1维时的ppp值,由于我们需要更新到第iii位,所以ppp需要右移一位,然后我们又选了uuuiii,因此还要加上uuu,也就是说,用uuu去跟新u+p/2u+p/2u+p/2

最后一维是SSS在二进制下111的个数。由于低于iii位不会受影响,只需要考虑iii位及以上的111的个数变化,原来从第iii位开始的数是p/2p/2p/2,现在第iii位开始的数是u+p/2u+p/2u+p/2,用到之前我们所求的ggg数组,111的个数变化量就是g[u+p/2]−g[p/2]g[u+p/2]-g[p/2]g[u+p/2]g[p/2],也就是说,用vvv去更新v+g[u+p/2]−g[p/2]v+g[u+p/2]-g[p/2]v+g[u+p/2]g[p/2]

那么状态转移方程就显而易见了:
dp[i][j][u+p/2][v+g[u+p/2]−g[p/2]]=∑u=0n∑p=0n∑q=0n(ju)viu⋅dp[i−1][j−u][p][q] dp[i][j][u+p/2][v+g[u+p/2]-g[p/2]] = \sum_{u=0}^{n} \sum_{p=0}^{n} \sum_{q=0}^{n} \binom{j}{u} v_i^u \cdot dp[i-1][j-u][p][q] dp[i][j][u+p/2][v+g[u+p/2]g[p/2]]=u=0np=0nq=0n(uj)viudp[i1][ju][p][q]
由于用i−1i-1i1去更新iii,所以要初始化i=0i=0i=0时,这很简单,由于j=uj=uj=up=0p=0p=0q=0q=0q=0,所以原方程可简化为:
dp[0][u][u][g[u]]=v0u dp[0][u][u][g[u]] = v_0^u dp[0][u][u][g[u]]=v0u
最终输出的答案就是:
∑p=0n∑q=0kdp[i][j][p][q] \sum_{p=0}^{n} \sum_{q=0}^{k} dp[i][j][p][q] p=0nq=0kdp[i][j][p][q]
最后几个细节:预处理viv_ivi的幂次和组合数,不然会TLE;对于dp[i−1][j−u][p][q]=0dp[i-1][j-u][p][q]=0dp[i1][ju][p][q]=0直接跳过,不跳也会TLE;时时刻刻取模,保险一点开longlonglong longlonglong在状态转移过程中枚举qqq的时候范围千万不要写成000kkk,否则只有303030分,因为中间过程SSS111的个数超过kkk不代表最终结果也超过kkk

AC代码~~(时间复杂度O(n4m)O(n^4m)O(n4m)):

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int g[105];//每一个数二进制下有几个1
long long v[105];
long long f[105][35][35][35];//动态规划数组
long long co[35][35];//组合数
long long MOD=998244353;//取模
long long po[105][35];//v的幂次
long long ans;//最终答案
int cal(int t){//计算每一个数二进制下有几个1
    int s=0;
    while(t){
        s+=(t&1);
        t>>=1;
    }
    return s;
}
int main(){
    cin>>n>>m>>k;
    for(int i=0;i<=m;i++){
        cin>>v[i];
    }
    for(int i=0;i<100;i++){//预处理g数组
        g[i]=cal(i);
    }
    for(int i=0;i<=n;i++){//初始化组合数
        co[i][0]=1;
        co[i][i]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            co[i][j]=(co[i-1][j]+co[i-1][j-1])%MOD;//计算组合数
        }
    }
    for(int i=0;i<=m;i++){
        po[i][0]=1;
        for(int j=1;j<=n;j++){
            po[i][j]=(po[i][j-1]*v[i])%MOD;//计算v的幂次
        }
    }
    for(int u=0;u<=n;u++){
        f[0][u][u][g[u]]=po[0][u];//初始化动态规划数组
    }
    for(int i=1;i<=m;i++){//枚举第i个数
        for(int j=0;j<=n;j++){//枚举总共选了j个数
            for(int u=0;u<=j;u++){//枚举当前选了u个数
                for(int p=0;p<=n;p++){//枚举S值右移i-1位的结果
                    for(int q=0;q<=n;q++){//枚举S中有q个1
                        if(f[i-1][j-u][p][q]==0){//没有值就跳过
                            continue;
                        }
                        f[i][j][u+p/2][q+g[u+p/2]-g[p/2]]=
                        (f[i][j][u+p/2][q+g[u+p/2]-g[p/2]]+
                        f[i-1][j-u][p][q]*po[i][u]%MOD*co[j][u]%MOD)%MOD;
                        //状态转移
                    }
                }
            }
        }
    }
    for(int p=0;p<=n;p++){
        for(int q=0;q<=k;q++){
            ans=(ans+f[m][n][p][q])%MOD;//累加结果
        }
    }
    cout<<ans;//输出
    return 0;
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐