AcWing(蓝桥杯1209)带分数(对dfs暴搜思想的个人笔记)

题目:

100 可以表示为带分数的形式:100=3+69258/714

还可以表示为:100=82+3546/197

注意特征:带分数中,数字 1∼9分别出现且只出现一次(不包含 0)。

类似这样的带分数,100 有 11 种表示法。

样例:

输入:

100

输出:

11

输入:

105

输出:

6

分析:题目告诉我们1~9都会出现且只出现一次,那么我们将123456789这些数字全排列后再在每个排列中选取每一个不同但连续的段来当作a,b,c,然后对a,b,c进行检查,查看是否满足n=a+b/c;如果满足那么就ans++;当然暴力全排列然后枚举是可以的,这里采用dfs+优化的方法来写:

我们可以用dfs来枚举未知数,由于未知数有3个,那么其实我们只用枚举两个就行,另一个未知数可以用其他两个和n的算式来代替。(就不需要3重dfs了,提升效率),我们采用嵌套dfs的方式,在枚举a的过程中不断去枚举c,再运用公式变形求出b(b=n*c-a*c);同时检查每一个abc能否符合题意,符合则答案+1。

代码:

#include<iostream>
#include<algorithm>
#include<queue>
#include<utility>
#include<cstring>
#include<stack>
#include<cmath>
#include<cstdio>
using namespace std;
typedef long long llint;

llint n,ans;//ans记录答案
llint vis[11],vis_b[11];//标记数组和拷贝标记数组
bool check(llint a,llint c){//检查函数
	llint b=n*c-a*c;//变形公式求出此时的b
	if(!a||!c||!b)//不能有一个数是0
	return false; 
	memcpy(vis_b,vis,sizeof vis);//将标记数组拷贝一下,用于判断标记
	while(b){//逐个取数
		llint x=b%10;
		b/=10;
		if(x==0||vis_b[x]){//判断b中的数字有没有被a和c选过
			return false;
		}
		else{
			vis_b[x]=1;//没有则标记为选过
		}
	}
	for(llint i=1;i<=9;i++){//最后再看一遍是不是1~9都选过
		if(!vis_b[i]){
			return false;
		}
	}
	return true;
}
void dfs_c(llint a,llint c){//在a的基础上对c进行dfs
	if(check(a,c)) ans++;//由于c是每次要搜完所有情况的,所以,当每个数都选择过了,自然会返回
	//如果通过检查发现此时的a和c满足题目要求,那么答案数++
    for(llint i=1;i<=9;i++){//和a一样的搜法,a,c共用一个标记数组,所以c不会拿a拿过的数字
		if(!vis[i]){
			vis[i]=1;
			dfs_c(a,c*10+i);//同理进入下一层
			vis[i]=0;
		}
	}
}
void dfs_a(llint a){
	if(a>=n){
		return;//如果a都已经大于n了,必然不符合题意,退出。
	}
	if(a){//当a是合法的时候,在a的基础上去dfs暴搜c
		dfs_c(a,0);
	}
	for(llint i=1;i<=9;i++){//开始搜索a
		if(!vis[i]){
			vis[i]=1;
			dfs_a(a*10+i);//如果当前这个数字没有出现过,那么把他加进a中,进入下一层。
			vis[i]=0;
		}
	}
}
int main(){
	cin >> n;
	dfs_a(0);//先dfs暴搜a的值
	cout << ans << endl;//输出方案数
	return 0;
}

此题简直是对暴搜的极致运用,无论是3重嵌套的dfs暴力还是优化的2重dfs搜索,都对dfs思维十分有帮助。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值