自己平时不大喜欢写题解啥的(太懒了……),但是难得发现一道网上没有题解的题啊哈哈哈哈,那我还是写下好了。
题意:↑↑↑↑↑↑↑↑↑点上面自己看吧↑↑↑↑↑↑↑↑↑↑↑
思路:看完了题目,是不是虽然可能没啥思路,但还是有点想法?提示一下,这题是被放在杭电集训队分块专题里面的一道题,所以这题应该莫队可解。
恩,反正我是不会用莫队解(表打我……),自己做这题的时候看了下BC官方的题解,官方题解说的是用f(x)表示[1,x]这个区间段里的答案,因此答案就是f(b)-f(a-1)了,题解里对这个函数是前面暴力枚举,后面用hash来储存和查找,然而我是渣渣,hash只会水题,因此自己只能看一下其他菊苣的代码了。
这里面有一位菊苣就是用的莫队,但是他的代码用了太多宏命令,自己读不动……所以接下来讲一个自己从排名第一的菊苣那里看到的数位dp的解法吧(我到底在扯什么……)
-----------------------分割线--------------------------
此题数位dp可解。
思路:对于每个f(x),用dfs枚举在其范围内符合要求的数,并记录,于是就得到答案。
关于时间复杂度,很容易看出没有剪枝的时间复杂度是O(10!*100)的,非常大,但是因为有了剪枝,时间复杂度变得不好估计了,这点很抱歉,我还是很弱,不会估计
具体操作见代码吧
自己说的可能不是特别清楚,见谅
#include <cstdio>
typedef long long LL;
LL a,b,s;
int k,N;
LL pow[20][20],argmt[20];
int pos[20];
//len:目前有几位数还没有被确定下来
//sum:要组合成的数
//top:目前在讨论的数字,先讨论9,再讨论8,直到0
LL dfs(int len,LL sum,int top){
if(len*pow[top][k]<sum) return 0;
if(sum<0) return 0;
if(top==0){
if(sum) return 0;
pos[0]=len;
LL ret=argmt[N];
for(int i=0;i<=9;++i) ret/=argmt[pos[i]];
//一个全排列就是符合要求的数的个数,因为首位是什么已经在solve()中的for里面决定好了
//所以后面首位是0也没关系,只要找到符合要求的数的个数,计算出它们可以组合出几种不同的数就好了
return ret;
}
LL ret=0;
for(int i=0;i<=len;++i){
pos[top]=i;
ret+=dfs(len-i,sum-i*pow[top][k],top-1);
}
return ret;
}
//在dfs前先做一些处理,比如剪枝,以及记录dfs开始前有几位数还没有被确定下来
inline LL sub_solve(int len,LL sum){
if(len==0) return sum==0;
N=len;
return dfs(len,sum,9);
}
int bit[20],len;
//solve(LL x):用来求[1,x]内符合范围数字的个数
LL solve(LL x){
len=0;
while(x) bit[++len]=x%10,x/=10;
LL ret=0,sum=s;
for(int i=len;i;--i){
for(int j=0;j<bit[i];++j)
ret+=sub_solve(i-1,sum-pow[j][k]);
//目前讨论的数没有取到最大值(j没有等于bit[i]),则后面的数可以随便取
sum-=pow[bit[i]][k];//这里表示sum取到了最大的值
}
ret+=(sum==0);
return ret;
}
int main(){
//先计算好排列数以及1到9内每个数的各种次方,后面会用到
argmt[0]=1;
for(int i=1;i<20;++i) argmt[i]=i*argmt[i-1];
for(int i=1;i<10;++i){
pow[i][0]=1;
for(int j=1;j<16;++j) pow[i][j]=pow[i][j-1]*i;
}
while(~scanf("%I64d%I64d%d%I64d",&a,&b,&k,&s)){
printf("%I64d\n",solve(b)-solve(a-1));
}
}