题意:
给你三个数字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; }