原题
试题 历届试题 带分数 http://lx.lanqiao.cn/problem.page?gpid=T26
问题描述
100 可以表示为带分数的形式:100 = 3 + 69258 / 714。
还可以表示为:100 = 82 + 3546 / 197。
注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。
类似这样的带分数,100 有 11 种表示法。
输入格式
从标准输入读入一个正整数N (N<1000*1000)
输出格式
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。
注意:不要求输出每个表示,只统计有多少表示法!
样例输入1
100
样例输出1
11
样例输入2
105
样例输出2
6
分析
1、实现全排列
9个数字不重复,自然想到了全排列。9!=362 880。这个穷举数量是1秒内是可以完成的。这里我直接通过 递归+标记 的方式实现全排列。当然,下面参考博客也提供了一种 不易理解 的实现方式,效率好像稍微高那么一点点。
2、巧妙判断是否为满足题意的等式
得到全排列的一种序列后,如何判断这个序列所对应的等式满足题意呢?
序列共有9位,我们用num[]数组来装,num[0] ~ num[8]。
(1)由于a <= n,所以 a的位数 <= n的位数
(2)yizhi我们根据(1)的条件,枚举a。又因为序列的最后一位 num[8],也是 c的最后一位。这样我们就可以推出 b的最后一位是: bLast = ((n-a) * num[8]) % 10;
(3)知道了b的最后一位,那么我们就可以在 a 的后面(起始位置可以更准确,因为 b的位数 >= c的位数)找是否存在 bLast。如果存在,则通过判断 (b%c == 0 && n == a + b/c) 是否满足题意。
效率
代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n;
int len = 0;
int num[15]; // 存放9个数字的全排列
int vis[15]; // 标志数字在之前是否已使用
int ans = 0;
// [start, end)
int getNum(int start, int end) {
int t = 0;
// 1 2 3
for(int i = start; i < end; i++) {
t = 10*t + num[i];
}
return t;
}
void print() {
for(int i = 0; i < 9; i++)
printf("%d ", num[i]);
printf("\n");
}
void dfs(int k) { // 0
if (k >= 9) {
// print();
// 得到一个全排列
for(int i = 1; i <= len; i++) { // a的长度必定在[1, len]
int a = getNum(0, i);
// n = a + b / c => b = (n - a) * c
int bLast = ((n - a)*num[8]) % 10; // b的最后一位
for(int j = (i-1)+ceil((9-i)/2); j < 8; j++) {
if(num[j] == bLast) {
int b = getNum(i, j+1);
int c = getNum(j+1, 9);
if(b%c == 0 && n == a + b/c) {
ans++;
}
break; // 可以提高效率
}
}
}
return;
}
for(int i=1; i<=9; i++)
if(vis[i]==0) { // 数字未被使用
num[k]=i; // 填入
vis[i]=1; // 标记已被使用
dfs(k+1); // 搜索下一个位置
num[k]=0;
vis[i]=0; // 解除标记
}
}
int main() {
scanf("%d", &n);
// 计算n有多少位,a的位数不可能多比n的位数
int t=n;
while(t!=0) {
len++;
t /= 10;
}
dfs(0);
printf("%d\n", ans);
return 0;
}