CF 给你三个数字L, R, K,问在[L, R]范围内有多少个数字满足它每一位不同数字不超过k个,求出它们的和(数位DP)

题意:

给你三个数字L, R, K,问在[L, R]范围内有多少个数字满足它每一位不同数字不超过k个,求出它们的和

 

分析:考虑用状态压缩 , 10给位0~9 , 如果之前出现过了某个数字x ,那就拿当前的状态 st | (1<<x) , 表示这个数字出现了 , 那st的二进制有多少的1 , 就有多少不同的数 , 这里好要考虑前导零的情况 。

个数是解决了 , 但是这里是要每个答案的和 , 贼鸡儿坑 , 经过前面的训练可以知道不可能是在(len==0) 这里判断的了 , 因为是记忆化搜索 , 所以你记忆化的只是数量 ,而不是权值和 , 我们可以开多一个位来统计当前位的权值和 , 我们也很容易可以发现 ,并不是单纯的相加起来 , 还要相乘与符合条件 ;

举例子:112和114都是满足条件的权值和 ;(112+114)=(100+10+2+100+10+4)=(2*100+2*10+2+4) 

 

 

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll MOD = 998244353ll;
int cnt[20];
ll ppow[22];
ll a,b,k;
struct Point{
    ll x,y;//x代表符合条件的有几个,y代表对答案的贡献
}dp[20][1<<12][2];
Point dfs(ll len,ll state,bool limit,bool non_zero){
    if(len==0) return Point{1,0};//一个数字枚举完了 符合条件的++ 不再产生贡献(之前已经计算了)
    if(!limit&&dp[len][state][non_zero].y) return dp[len][state][non_zero];
    //记忆化
    Point ans = Point{0,0};//初始化ans
    int Max = limit?cnt[len]:9;//套路
    for(int i=0;i<=Max;++i){
        ll temp = state|((non_zero||i)<<i); //改变状态
        if(__builtin_popcountll(temp)>k) continue;//删掉错误的状态
        Point t = dfs(len-1,temp,limit&&i==Max,non_zero||i);//临时变量
        ans.x = (ans.x+t.x)%MOD;//符合条件的个数增加
        ans.y = (ans.y+t.y+i*ppow[len-1]%MOD*t.x%MOD)%MOD;//当前数位的贡献增加
    }
    return dp[len][state][non_zero]=ans;
}
ll solve(ll x){
    memset(dp,0,sizeof dp);
    memset(cnt,0,sizeof cnt);
    int len=0;
    while(x){
        cnt[++len]=x%10;
        x/=10;
    }
    return dfs(len,0,true,0).y;
    //最高位开始枚举 现在还没有任何数位上有数字 到达了最高位 有前导零zero=true non_zero = false
}
int main(){
    ppow[0]=1;
    for(int i=1;i<20;++i) ppow[i]=ppow[i-1]*10%MOD;
    ios::sync_with_stdio(0);
    cin>>a>>b>>k;
    cout<<(solve(b)-solve(a-1)+MOD)%MOD<<endl;
    return 0;
}
View Code

 

posted @ 2018-11-16 09:54  shuai_hui  阅读(910)  评论(0)    收藏  举报