带分数(不懂)

带分数(不懂)

题干

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

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

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

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

输入格式
一个正整数。

输出格式
输出输入数字用数码 1∼9 不重复不遗漏地组成带分数表示的全部种数。

输入样例1
100

输出样例1
11

输入样例2
105

输出样例2
6

数据范围
1 ≤ N < 106

代码

package aJAVA13;

import java.util.Scanner;

public class _08带分数 {
	static int ans;
	static int N;
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		
		Scanner sc = new Scanner(System.in);
		N =sc.nextInt();
		int[] arr = {1,2,3,4,5,6,7,8,9};
		f(arr,0);
		System.out.println(ans);

	}
	//现在确认第几位
	private static void f(int arr[],int k){
		if(k == 9) {
			check(arr);
//			print(arr);
			return;
		}
	
//		将第i位和第k位交换
		for (int i = k; i < arr.length; i++) {
			int t = arr[i];
			arr[i] = arr[k];
			arr[k] = t;
//			移交给下一层去确认第k+1位
			f(arr,k+1);
//			回溯
			t = arr[i];
			arr[i] = arr[k];
			arr[k] = t;

		}

		
	}
//	枚举加号和除号的位置
	public static void check(int [] arr) {
		//加号前的字符数最多是7
		for (int i = 1; i <= 7; i++) {
			int num1 = toInt(arr,0,i);//+前面的一段数值
			if(num1>=N)//如果此时加号前的数额已经超过了n,没必要算了
				continue;
			//除号前面的字符数也至少是一个
			for (int j = 1; j <= 9-i-1; j++) {
				int num2 = toInt(arr,i,j);
				int num3 = toInt(arr,i+j,9-i-j);
				if(num2%num3==0&&num1+num2/num3==N) {
					ans++;
				}
			}
			
		}
	}
	public static void print(int []arr) {
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	private static int toInt(int arr[],int pos,int len) {
		int t = 1;
		int ans = 0;
		for (int i = pos+len-1; i >= pos; i--) {
			ans+=arr[i]*t;
			t*=10;
		}
		return ans;
	}

}

总结

对题目进行分析,需要满足以下两个条件:

  1. 数字1~9每个数字都要出现一次,也仅能出现一次
  2. 三个数中不能有包括0

观察可以知道n的带分数的第一个加数a是从1到n-1,第二个加数由两部分b和c组成b/c;遍历b、c可以用它们的倍数;每次遍历得到的a、b、c都进行个数判断,只有满足个数和为9的条件,再判断是否数字是否都出现仅出现一次,这里我借助了一个长度为9的数组进行计数

补充
发现用全排列的思想,切割着去做更方便,重点就是思考b、c中间的切点,可以巧妙的利用n=a+b/c这个公式,化成b=(n-a)*c,知道n的最后一位数字,a的最后一位数字,c的最后一位数字,就可以判断b的最后一位数字是什么了,再进行满足条件的判断。下面是原文章:

分析:这道题初看起来第一感觉就是用暴力破解应该可以搞定,但是时间复杂度应该会相当可观,仔细观察,会发现这道题无非是全排列的一种运用,把等式定义为:

N=A+B/C ,则ABC组合在一起就是1到9的一个全排列,所以可以把问题转换成对于一个9位数的数字,如何将其划分为A、B、C三部分,使得其满足N=A+B/C(隐含条件:B%C==0),对于一个9位数可以这样考虑:A是不可能大于N的,所以A的位数只可能是从1位到和N相同位数这个范围,确定了A的位数之后,剩下的就是B和C的总位数,

B数字的开始位置即A数字的下一位,C数字的最后一位就是整个9位数的最后一位,那如何确定B的结束位置呢?

这里有个小技巧,可以大大减少可能性的判断:

假设A的结束位置为 aEnd,则aEnd+1~9就是B和C的位置,在这个位置范围内,B最少占据了一半的数字,否则B/C就不能整除了,所以可以从aEnd+1~9的中间位置开始

确定B的结束位置,这时候可以从中间位置开始向后确定B的结束位置,一直到8的位置,确定了B的结束位置,则A、B、C三个数字的具体值就都可以确定了,判断是否符

合等式N=A+B/C,符合则输出。

以上确定B的结束位置的方法其实不怎么好,因为还是会浪费一些时间(自己模拟下就知道了),不过已经可以在规定的时间内得出答案了。

这里再介绍一种确定B结束位置的方法,可以让性能再提高一些:

观察等式:N=A+B/C,可以转换成==》B=(N-A)*C,N和A确定了(先确定A的结束位置后,再来确定B的结束位置的),C的最后一个数字确定了(整个9位数最后一位),即可以确定B最后一个数字了(这里将其定义为BL),这样可以从以上的aEnd+1~9的中间位置开始找,直到8,当数字为BL时,则判读是否符合等式:N=A+B/C,可以想想,其实这种等于BL的位置至多有一次(因为数字1到9不能重复出现),所以第一次找到和BL匹配的数字的时候就不用再往后找了。用这种方法,提高的性能还是非常可观的!

import java.util.Scanner;
 
public class ys_09 {
	
	public static void main(String[] args) {
		//将等式定义为:N=A+B/C
		Scanner scanner=new Scanner(System.in);
		N=scanner.nextInt();
		long start=System.currentTimeMillis();
		int[] s=new int[]{1,2,3,4,5,6,7,8,9};
		NLength=(N+"").length();
		allRange(s, 0, s.length-1);
		long end=System.currentTimeMillis();		
		System.out.println("耗时:"+(end-start)+" ms");
		System.out.println("总数为:"+kinds+" 种");	
	}
	public static int N;
	public static int NLength;//N数字的长度
	public static int kinds;
	public static void process(int[] s){
		String str="";
        for(int i=0;i<9;i++) str+=s[i];
		int A,B,C,NMA,BC,BMCL,BLastNumber;
		//A的位数
		for(int i=1;i<=NLength;i++){
			
			/*
			//方法1
			A=Integer.valueOf(str.substring(0, i));
			NMA=N-A;//N减去A的值
			if(NMA<=0)return;
			BC=9-i;//B和C还有多少为可用
			BMCL=(NMA+"").length();//B/C的长度 
			//确定的B的结束为止
			for(int j=i+BC/2;j<=8;j++){//可以优化这里
				B=Integer.valueOf(str.substring(i,j));
				C=Integer.valueOf(str.substring(j,9));
				if(B%C==0&&B/C==NMA){
					kinds++;
					System.out.println(N+"="+A+"+"+B+"/"+C);
				}
			}
			*/
			
			//方法2
			A=Integer.valueOf(str.substring(0, i));
			NMA=N-A; 
			if(NMA<=0)return;
			BC=9-i;//B和C总共多少位
 
			BLastNumber=(NMA*s[8])%10;//B最后的数字
			//j为B最后一个数字的位置
			//B最少占有B和C全部数字的一半,否则B/C不可能为整数
			for(int j=i+BC/2-1;j<=7;j++){
				//找到符合的位置
				if(s[j]==BLastNumber){
					B=Integer.valueOf(str.substring(i,j+1));
					C=Integer.valueOf(str.substring(j+1,9));
					if(B%C==0&&B/C==NMA){
						kinds++;
						System.out.println(N+"="+A+"+"+B+"/"+C);
					}
					//符合要求的位置只可能出现一次
					break;
				}
			}
		}
	}
	public static void swap(int[] s,int a,int b){
		if(a==b)return;
		int tmp=s[a];
		s[a]=s[b];
		s[b]=tmp;
	}
	//全排列
	public static void allRange(int[] s,int k,int m){
		if(k==m){
			process(s);
			return;
		}
		else{
			for(int i=k;i<=m;i++){
				swap(s,k,i);
				allRange(s, k+1, m);
				swap(s,k,i);
			}
		}
	}
}

1.100 = 3 + 69258 / 714

观察右边 这个分数,发现分子一定是比分母大的,并且一定要整除,否则无法约分出现整数。

问你有多少种的?枚举,合格计数不合格不计数。递归框架求全排列。

2.回溯

平行状态之间切换需要回溯原来的状态,带着记忆的话就会受影响,每一个都是原来的镜像,同级之间切换需要恢复到原来的状态。

image-20210311192449850

3.加号前的字符串最长为7,再多了除号放不开了

image-20210311194456060

除号:

image-20210311194808095 image-20210311195923695

​ int num2 = toInt(arr,i,j);

image-20210314194436965

int num3 = toInt(arr,i+j,8-i-j);

数组中的多个char元素变为Int

效率:

注:部分转载https://blog.csdn.net/keepthinking_/article/details/8947014

https://www.bilibili.com/video/BV1GE411F7Pj?p=8&t=1815

posted @ 2021-03-14 20:05  记录学习Blog  阅读(408)  评论(0编辑  收藏  举报