“蔚来杯“2022牛客暑期多校训练营7-J Melborp Elcissalc

"蔚来杯"2022牛客暑期多校训练营7-J Melborp Elcissalc

原题题面:https://ac.nowcoder.com/acm/contest/33192/J

题目大意

已知 k ( 1 ≤ k ≤ 64 ) k(1\le k\le 64) k(1k64),对于每个仅包含 [ 0 , k − 1 ] [0,k-1] [0,k1]区间内的整数的数组,定义其优美度为非空连续子数组之和为 k k k的倍数的数量。

求有多少长度为 n ( 1 ≤ n ≤ 64 ) n(1\le n\le 64) n(1n64)的数组,其优美度为 t ( 0 ≤ t ≤ n ( n + 1 ) 2 ) t(0\le t\le\frac{n(n+1)}{2}) t(0t2n(n+1)),答案对 998244353 998244353 998244353取模。

解题思路

考虑如何求给定一个序列,求其中和为 k k k的倍数的区间的个数,先求一遍前缀和,然后各取模 k k k,不影响结果 ,求出 s u m i = ( s u m i − 1 + a i ) % m o d sum_i=(sum_{i-1}+a_i)\%mod sumi=(sumi1+ai)%mod

发现若任意 s u m i = s u m j ( i ≤ j ) sum_i=sum_j(i\le j) sumi=sumj(ij),则区间 [ i , j ] [i,j] [i,j]的和为 k k k的倍数,定义 n u m i num_i numi s u m j = i ( j ∈ [ 1 , n ] ) sum_j=i(j\in [1,n]) sumj=i(j[1,n]) j j j的个数,则一段序列中,和为 k k k的倍数的区间的个数显然为:
∑ i = 0 k − 1 C n u m i 2 \sum_{i=0}^{k-1}C_{num_i}^2 i=0k1Cnumi2

d p i , j , k dp_{i,j,k} dpi,j,k表示已使用区间和为 [ 0 , i ] [0,i] [0,i],占了 j j j个位置,优美度为 k k k的个数, s s s为区间和为 i i i所占的位置数,则 d p i , j , k dp_{i,j,k} dpi,j,k要加上 s s s 0 0 0 j j j d p i − 1 , j − s , k − C s 2 dp_{i-1,j-s,k-C_s^2} dpi1,js,kCs2与这 s s s个位置的方案数 C n − j + s s C_{n-j+s}^s Cnj+ss的积。即:
d p i , j , k = ∑ s = 0 j d p i − 1 , j − s , k − C s 2 × C n − j + s s dp_{i,j,k}=\sum_{s=0}^jdp_{i-1,j-s,k-C_s^2}\times C_{n-j+s}^s dpi,j,k=s=0jdpi1,js,kCs2×Cnj+ss
答案即为: d p k − 1 , n , t dp_{k-1,n,t} dpk1,n,t

代码实现

时间复杂度为 O ( N 5 ) O(N^5) O(N5),勉强过。空间复杂度可能过大,考虑到一次转移只与 i − 1 i-1 i1有关,可以用滚动数组。或考虑到 j j j是从小转移到大,可以先从 d p i , n , k dp_{i,n,k} dpi,n,k开始转移。

有dalao写了极其fast的代码,可以去看看。

数据卡常,注意常数优化

#include<bits/stdc++.h>
using namespace std;
const int N=66,mod=998244353;
int n,k,t,dp[N+1][N*(N+1)/2],fac[N+1][N+1],K;
int add(int a,int b){
    if(a+b>=mod)return a+b-mod;
    return a+b;
}
void init(){
    fac[0][0]=1;
    for(int i=1;i<N;i++)
    {
        fac[i][0]=1;
        for(int j=1;j<=i;j++){
            fac[i][j]=add(fac[i-1][j],fac[i-1][j-1]);
        }
    }
}
int C(int x,int y){
    if(x<y)return 0;
    return fac[x][y];
}

int main(){
    ios::sync_with_stdio(false);
    cin>>n>>K>>t;
    init();
    for(int y=0;y<=n;++y){
        dp[y][C(y+1,2)]=C(n,y);
    }
    for(int i=1;i<K;i++){
        for(int j=n;j>=0;j--){
            for(int k=t;k>=0;k--){
                for(int s=1;s<=j&&k-C(s,2)>=0;s++){
                    dp[j][k]=add(dp[j][k],1ll*dp[j-s][k-C(s,2)]*C(n-j+s,s)%mod);
                }
            }
        }
    }
    cout<<dp[n][t];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值