题目链接: AcWing 1209 带分数
题意: 给定一个数$N$,问有多少组$a,b,c$满足$a+\dfrac bc=N$,且$a,b,c$三个数不重不漏地涵盖$1-9$这$9$个数字,输出总组数
解题思路:
- 暴力枚举出$9$个数的全排列,然后用一个长度为$9$的数组保存全排列的结果
- 从全排列的结果中用两重循环暴力分解出三段,形如下图,通过 $i,j$ 将一个排列分割,每段代表一个数
- 验证枚举出来的三个数是否满足题干条件,若满足则计数
代码如下:
#include <iostream>
using namespace std;
const int N = 10;
int target; // 题目给出的目标数
int num[N]; // 保存全排列的结果
bool used[N]; // 生成全排列过程中标记是否使用过
int cnt; // 计数,最后输出的结果
// 计算num数组中一段的数是多少
int calc(int l, int r) {
int res = 0;
for (int i = l; i <= r; i++) {
res = res * 10 + num[i];
}
return res;
}
// 生成全排列
// 当全排列生成后进行分段
void dfs(int u) {
// 用两层循环分成三段
if (u == 9) {
for (int i = 0; i < 7; i++) {
for (int j = i + 1; j < 8; j++) {
int a = calc(0, i);
int b = calc(i + 1, j);
int c = calc(j + 1, 8);
// 注意判断条件,因为C++中除法是整除,所以要转化为加减乘来计算
if (a * c + b == c * target) {
cnt++;
}
}
}
return;
}
// 搜索模板
for (int i = 1; i <= 9; i++) {
if (!used[i]) {
used[i] = true; // 标记使用
num[u] = i;
dfs(u + 1);
used[i] = false; // 还原现场
}
}
}
int main() {
scanf("%d", &target);
dfs(0);
printf("%d\n", cnt);
return 0;
}
- UPD:2022-01-10
- 下面再给出一个直接调用
next_permutation()
函数的做法,可以代替手写暴搜来枚举全排列,蓝桥杯是可以使用这个函数的。 - 另外评论区下面很多初学的同学都比较疑惑循环边界问题,事实上像下面的代码一样,都写成全排列数组的个数( $9$ )也是一样的,只不过上面的代码在写的时候处理得更加细致一些,实际上没必要,像下面这么写就可以了。
- UPD:2022-03-04
- 之前的代码有点小问题,在判断时,需要将 $a,b,c$ 为 $0$ 的情况特判掉。
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int target;
int num[N];
int calc(int l, int r) {
int res = 0;
for (int i = l; i <= r; i++) {
res = res * 10 + num[i];
}
return res;
}
int main() {
cin >> target;
for (int i = 0; i < 9; i++) {
num[i] = i + 1;
}
int res = 0;
do {
for (int i = 0; i < 9; i++) {
for (int j = i + 1; j < 9; j++) {
int a = calc(0, i);
int b = calc(i + 1, j);
int c = calc(j + 1, 8);
if (a == 0 || b == 0 || c == 0) {
continue;
}
if (a * c + b == c * target) {
++res;
}
}
}
// 调用函数生成全排列
} while (next_permutation(num, num + 9));
cout << res << '\n';
return 0;
}
全排列写的滚瓜烂熟的我看到这题直接犯傻 唉什么时候才能向你们一样强
加油hh
把题目转换成学到的东西才是最难的 哎
现在的我似乎变强了,一看到这种题就知道怎么入手了
我也背的滚瓜烂熟,但是碰到换汤不换药的题还是得看题解要不然看一天也看不出来QAQ
%%%我也想变强QAQ
大佬请问你变秃了没有
厉害hh
我还是个傻逼
全排列有模板在哪吗
那是真的强了
大佬 问一下怎么做到的 是多刷题吗呜呜呜
大佬怎么刷题变强啊
序列长度为 9:由于使用数字 1 至 9,全排列生成的序列长度恒定为 9。
为何第一层循环到 7:第一层循环负责分割出第一部分 a 和剩下的部分(后续将被分为 b 和 c)。因为 a, b, c 都必须是非空的,所以 a 的最大长度可以是 7(这样留给 b 和 c 至少各有一个数字)。i < 7(循环到 7)确保了至少留下两位数字可以分给 b 和 c。
为何第二层循环到 8:在 a 被分割出去之后,剩下的部分需要被进一步分割为 b 和 c。第二层循环控制这个分割点。因为 b 和 c 也都必须是非空的,所以当 a 分割后,剩余部分的第一个数字归 b,最后一个数字归 c,至少留一个数字给 c。j 从 i + 1 开始循环到 8,确保了至少 b 和 c 各自有一位数字。
谢谢!
感谢大佬,第一 编看完y总的视频,完全弄不懂,仔细看一遍你写的题解就能懂点并且能自己写出来了
我第一遍感觉我完全学不懂,都想摆烂了,狂说算法狗都不学
为什么a不取8和9这两个数字也能ac呢?样例100a取了数字8和2啊,有一种情况是要取8的,在代码中a就取不到8,那这个样例怎么算出来的呢
这里的 $i,j,k$ 并不是指取 $i,j,k$ 这几个 数字 ,而是取一个排列的 位置 。比如通过深搜枚举出一个 $1$ 到 $9$ 的排列(用题面上那个例子): $(8,2,3,5,4,6,1,9,7)$ ,然后 $i=1$ 表示取这个排列的 $0$ 到 $i$ 的位置,也就是 $(8,2)$ ,得到的结果就是 $82$ ,后面 $j,k$ 同理。
懂了,num数组里的数字不是永久的1,2,3,4,5,6,7,8,9,而是可以变动的,而a只取前6位,所以a是可以取到1~9每个数字的,随着num的改变a可取不同的值
牛逼,我一开始还以为你少了一些情况呢。膜拜大佬
你i从0~6,这是不是说明a只能取1~7这7个数字,但是a可以取8,9这两个数字啊,,例如样例100,a取了82,但是代码中我怎么看不出来a可以取8这个数字呢?没懂,求大佬解答
取的是全排列结果的下标
问一下,有比赛是不支持next_permutation()这个函数的吗
插个眼
从C++98就开始支持了,现在主流比赛的编译器基本都是14、17甚至20了,所以不用担心。
c++11可以吗
谢谢你
// 注意判断条件,因为C++中除法是整除,所以要转化为加减乘来计算
有大佬能解释下这个注释是为什么吗?
因为c++除法是不看小数点后面的数字的,不转为乘法会出现 n=c+b(n=c,b=0的情况,列子1=1+2/3456789,满足除法,不满足条件,所以除法的题一般都要转乘法。)
哦哦哦万分感谢!
nb
每个数字只能用一次啊,不会存在n=c的情况吧
左边是n,是给定的,不是要进行全排的
感谢.
.为什么要写成do,while,不直接写while,无条件先执行一次的目的是什么呢
因为你初始化的时候数组是一种情况,直接全排列的话第一种情况直接就少掉了
是的,这是用
next_permutation()
的一个固定方式。谢谢
谢谢
谢谢额
res = res * 10 + num[i];这句是什么作用啊 我没看懂 大佬教教
数组中每个元素都是单独一个个位数,然后比如说$num[2]\dots num[3]$,就看作是$num[2]\dots num[3]$之间的这$2$个个位数连接成一个两位数对吧,组成的这个两位数应该是$num[2]\times 10 + num[3]$,这个操作相当于是做加法进位
和我的一样,y总写的优化后的我有点不懂代码,哪个大神能画一份递归搜索树,拜托了
是不是超时了,我的运行后显示4800多ms
orz orz orz orz orz orz
这时间复杂度有多大啊
阶乘,也就是 $9!$ ,再加上验证的两层循环,不会超过 $10^8$。
orz,思路是真清楚,一目了然
我去。。。整除有精度损失,太细了,难怪我100输出了五千多个,被折磨了一个晚上,原来是因为这个。。。唉基础不牢,地动山摇
感谢大佬的讲解,看的时候人都傻了,看了思路写出来了
666
学到了·