HDU 5085 Counting problem

自己平时不大喜欢写题解啥的(太懒了……),但是难得发现一道网上没有题解的题啊哈哈哈哈,那我还是写下好了。

题意:↑↑↑↑↑↑↑↑↑点上面自己看吧↑↑↑↑↑↑↑↑↑↑↑

思路:看完了题目,是不是虽然可能没啥思路,但还是有点想法?提示一下,这题是被放在杭电集训队分块专题里面的一道题,所以这题应该莫队可解。
恩,反正我是不会用莫队解(表打我……),自己做这题的时候看了下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));
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值