Educational Codeforces Round 53 (Rated for Div. 2) E. Segment Sum

https://codeforces.com/contest/1073/problem/E

题意

求出l到r之间的符合要求的数之和,结果取模998244353
要求:组成数的数位所用的数字种类不超过k种

思路

这题一看就是个数位dp的模板题,但是由于以前没有完全理解数位dp加上xjb套模版,导致样例都没算出来

  • 一开始这样定义状态

    dp[i][j] 代表第cnt~i(高位到低位)位,cnt~i状态为j的总和

  • 一直记录当前数字的数值,到边界的时候返回加到答案中,但是由于这样定义状态会导致一个状态对应多个数,但是他只能累加字典序最小的数到答案中,所以会使得有的数并没有被计算

  • 仔细分析一波,回归dp本身,应该找到一个子状态,使得后面的状态可以通过前面的状态计算得到(转移),其实只需要改一下定义

    dp[i][j] 代表0~i(低位到高位),cnt~i+1状态为j的总和sum和数字个数num(dp两个东西)
    在第i步选择第i位的数字

  • 其实就是转换一下计算方法,比如0~9可以加在数字1后,组成10~19,转化为公式\(10^{i-1}*dp[i-1][j|(1<<digit)].num*digit\)
  • 转移为:\(dp[i][j]=\sum_{digit=0}^{9}(dp[i][j].sum+10^{i-1}*dp[i-1][j|(1<<digit)].num*digit)\)

关于数位dp

  • 对于每个数求出的是小于这个数的所有满足条件的数(前缀和)
    • 将一个数当成字符串,然后从高位往低位看,lead看前导0是否对计数产生影响
    • 通常定义状态可以理解为

      dp[i][j] 代表0~i(低位到高位),cnt~i+1状态为j的计数

  • 关于lead和limit,可以放在数组下标,也可以在函数里特判

附上一个lead和limit在函数中特判的板子

#include<bits/stdc++.h>
#define ll long long 
#define P 998244353
#define M 2005
using namespace std;
struct N{
    ll x,y;
}dp[25][M];
ll pw[25],l,r;
int k,a[25],i,cnt;

N dfs(int p,int st,int lead,int limit){
    if(!p) return __builtin_popcount(st)<=k&&!lead?N{1,0}:N{0,0};
    if(!lead&&!limit&&dp[p][st].y!=-1)return dp[p][st];
    N ans=N{0,0};
    int end=(limit?a[p]:9);
    for(int i=0;i<=end;i++){
       N tp=dfs(p-1,lead&&!i?0:st|(1<<i),lead&&!i,limit&&i==end);
       ans.x=(ans.x+tp.x)%P;
       ans.y=(ans.y+tp.y+1ll*i*pw[p-1]%P*tp.x%P)%P;
    }
    if(!limit)dp[p][st]=ans;
    return ans;
}
    

ll cal(ll x){
     cnt=0;
    while(x>0){a[++cnt]=x%10;x/=10;}
    memset(dp,-1,sizeof(dp));
    return dfs(cnt,0,1,1).y%P;
}

int main(){
    pw[0]=1;
    for(i=1;i<=20;i++)pw[i]=pw[i-1]*10%P;
    cin>>l>>r>>k;
    cout<<(cal(r)-cal(l-1)+P)%P;
}

猜你喜欢

转载自www.cnblogs.com/VIrtu0s0/p/9895573.html