1. 程式人生 > >Educational Codeforces Round 53 (Rated for Div. 2) E. Segment Sum

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;
}