Educational Codeforces Round 53 (Rated for Div. 2) E. Segment Sum (数位dp求和)

题目链接:https://codeforces.com/contest/1073/problem/E

题目大意:给定一个区间[l,r],需要求出区间[l,r]内符合数位上的不同数字个数不超过k个的数的和(并且模998244353)

例如求区间[10,50],k=1,答案为ans=(11+22+33+44)%998244353=110.

Examples

input
Copy
10 50 2
output
Copy
1230
input
Copy
1 2345 10
output
Copy
2750685
input
Copy
101 154 2
output
Copy
2189


解题思路:首先我们用数位dp求出区间[l,r]上合法数的个数并不难,可以采用二进制记录每一个数是否出现,难点在于如何对合法的数进行求和求和,我们肯定不能一个数一个数进行求和,,我们可以考虑在每个位放每个数,计算该位的该数对答案的贡献度。总权值和等于比它低位的权值和+当前位的权值。
需注意前导0的情况。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const ll mod=998244353;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int lcm(int a,int b){return a/gcd(a,b)*b;}
int a[20],k;
ll l,r;
struct node{
    ll a,b;  //a表示合法数的个数,b表示权值的和 
}dp[20][2525];
int work(int x){  //计算数位中有多少个不同的数字 
    int cnt=0;
    for(int i=0;i<=10;i++)
    if(x>>i&1)cnt++;
    return cnt;
} 
node dfs(int pos,int sta,int limit,bool invalid){
//sta记录包含的不同数,invalid记录前导0 
    if(pos==0) return node{1,0}; 
    if(!limit&&dp[pos][sta].a!=0)
    return dp[pos][sta];
    int up=limit?a[pos]:9;
    node ans,tmp;
    ans.a=0; ans.b=0;  //局部变量得初始化 
    for(int i=0;i<=up;i++){
        int x=sta|(int)(pow(2,i)); //更新状态 
        if(work(x)>k) continue; //不同数字个数超过k,不合法剪枝 
        if(invalid&&i==0){  //前导都为0,状态不用更新 
            tmp=dfs(pos-1,sta,limit&&i==up,invalid&&i==0);
        }else{
            tmp=dfs(pos-1,x,limit&&i==up,invalid&&i==0);
        }
        tmp.a%=mod; tmp.b%=mod;
        ans.a=(ans.a+tmp.a)%mod; //累计合法数的个数 
        ll y=pow(10,pos-1);
        ans.b=(ans.b+tmp.b+1ll*y%mod*i%mod*tmp.a%mod)%mod; //累计合法数的权值 
    }
    if(!limit)
    dp[pos][sta]=ans;
    return ans;
}
ll solve(ll x){
    int pos=0;
    while(x){
        a[++pos]=x%10;
        x/=10;
    }
    return dfs(pos,0,1,true).b;
}
int main(){
    cin>>l>>r>>k;
    cout<<(solve(r)-solve(l-1)+mod)%mod<<endl; //防止答案为负 
    return 0;
} 

 

posted @ 2019-03-31 13:59  两点够吗  阅读(199)  评论(0编辑  收藏  举报