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

题目来源:https://ac.nowcoder.com/acm/contest/33192/J

题意

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

分析

对于本题,首先考虑如何统计一个数组中和为和为k的倍数的区间数量。很容易能够想到使用前缀和,对于前缀和数组中 p r e l pre_l prel p r e r pre_r prer,若 ( p r e l − p r e r ) (pre_l-pre_r) (prelprer) m o d mod mod k = 0 k=0 k=0,则 [ l + 1 , r ] [l+1,r] [l+1,r]区间的和为k的倍数。不妨将前缀和数组中的每个值 m o d mod mod k k k,就能够简化为若 p r e l = p r e r pre_l=pre_r prel=prer,则 [ l + 1 , r ] [l+1,r] [l+1,r]区间的和为k的倍数。由于对于每个前缀和数组,有且仅有一个原数组与之对应,因此我们可以将统计原数组中的和为k的倍数的区间数量,转化为统计前缀和数组中两两相等的元素组数量。

s u m i sum_i sumi为前缀和数组中元素 i i i出现次数,则该数组的优美度为: ∑ i = 0 k − 1 C s u m i 2 \sum\limits_{i=0}^{k-1}C_{sum_i}^2 i=0k1Csumi2

我们利用动态规划分别统计每个 s u m i sum_i sumi对优美度的贡献,设 d p i , j , k dp_{i,j,k} dpi,j,k表示已经考虑了 [ 0 , i ] [0,i] [0,i]区间的数,填充了数组中的 j j j个位置,已填充的数对于优美度的贡献为 k k k的数组数量。若取 j j j个数字 i i i填入数组,则其对于优美度的贡献为 C j 2 C_j^2 Cj2,由此我们可以得到转移方程: 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\limits_{s=0}^{j}dp_{i-1,j-s,k-C_s^2} \times C_{n-(j-s)}^s dpi,j,k=s=0jdpi1,js,kCs2×Cn(js)s其中 s s s为已填充的 i i i的数量, C n − ( j − s ) s C_{n-(j-s)}^s Cn(js)s为在未填充的 n − ( j − s ) n-(j-s) n(js)个位置中,在不同位置放入 s s s i i i的总方案数。

对于初始化,由于在数组中填入 i i i 0 0 0对于优美度的贡献为 C i + 1 2 C_{i+1}^2 Ci+12,且这样的数组有 C n i C_n^i Cni个,因此将所有的 d p 0 , i , C i + 1 2 ( 0 ⩽ i ⩽ n ) dp_{0,i,C_{i+1}^2}(0\leqslant i\leqslant n) dp0,i,Ci+12(0in)赋值为 C n i C^i_n Cni即可。

注意本题时间限制较紧,注意优化常数。

参考代码


#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const ll mod=998244353;
ll C[70][70];
ll dp[70][70][4900];//使用了[0,i]区间的数,填充了j个位置,优美度为k的数组数量
ll n,k,t;
void Triangle(){
    C[0][0]=1;
    for(int i=1;i<70;++i){
        C[i][0]=1;
        for(int j=1;j<=i;++j)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
} 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    cin>>n>>k>>t;
    Triangle();
    for(ll i=0;i<=n;i++)
        if(C[i+1][2]<=t)dp[0][i][C[i+1][2]]=C[n][i];
    for(ll i=1;i<k;i++)
        for(ll j=0;j<=n;j++)
            for(ll l=0;l<=t;l++) 
                for(ll s=0;s<=j;s++)//有几个i
                    if(l>=C[s][2])   
                        dp[i][j][l]=((dp[i][j][l]+dp[i-1][j-s][l-C[s][2]]*C[n-j+s][s]%mod)>=mod)?((dp[i][j][l]+dp[i-1][j-s][l-C[s][2]]*C[n-j+s][s]%mod)-mod):(dp[i][j][l]+dp[i-1][j-s][l-C[s][2]]*C[n-j+s][s]%mod);
						//优化常数,等同于 dp[i][j][l]=dp[i][j][l]+dp[i-1][j-s][l-C[s][2]]*C[n-j+s][s]%mod
	cout<<dp[k-1][n][t];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值