蓝桥杯第二次校园模拟赛(2020年)

第一题

题目

问题描述
  在计算机存储中,12.5MB是多少字节?
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。
  本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案

	计算机存储单位的换算,常见的有:
	bit(b,),byte(B,字节),Kilo Byte(KB)
	Mega Byte(MB),Giga Byte(GB)
	Tera Byte(TB)
	只有1B=8b,其他的都是1024进制
	所以:
	12.5MB=12.5*1024*1024B
	13107200

第二题

题目

问题描述
  由1对括号,可以组成一种合法括号序列:()。
  由2对括号,可以组成两种合法括号序列:()()(())。
  由4对括号组成的合法括号序列一共有多少种?
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。
  本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案

14

package competition2;

public class Main7OfMy
{
	/*
	 * 这是一个递归的问题,每一个位置都有两种选择,要么是左括号 
	 * 要么是右括号,但是放括号有两个条件约束: 
	 * 1.左右括号剩下的数量都大于0才可以继续放括号,
	 * 2.要保证放下的括号后,左括号数量永远要大于或等于右括号的数量
	 */
	public static void main(String[] args)
	{
		System.out.println(Choice(4, 4, 4));
	}

	static public int Choice(int number, int l, int r)
	{
		int count = 0;
		if (l == 0 && r == 0)
		{
			return 1;
		}
		if (l > 0 && (number - (l - 1) >= number - r))
		{
			count += Choice(number, l - 1, r);
		}
		if (r > 0 && (number - l >= number - (r - 1)))
		{
			count += Choice(number, l, r - 1);
		}
		return count;
	}
}

第三题

题目

问题描述
一个包含有2019个结点的有向图,最多包含多少条边?(不允许有重边)
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。
本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案

4074342

2019个结点,每两个就能组成一个有来有回的组合,那么最多就是Cn2=n(n-1)/(2*1)
那么就是2019*2018/2=4074342

第四题

题目

问题描述
  将LANQIAO中的字母重新排列,可以得到不同的单词,
  如LANQIAO、AAILNOQ等,注意这7个字母都要被用上,单词不一定有具体的英文意义。
  请问,总共能排列如多少个不同的单词。
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。
  本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案

2520

这里说四种思路,第一种:这个是全排列的问题,而且刚好发现里面只有A是出现了两次,
其他的都是只出现一次,那么首先不考虑重复出现的字母,全排列的次数是7*6*5*4*3*2*1=5040
但是因为有两个A是一样的,所以有一半是重复的,所以结果是5040/2=2520
package competition2;

import java.util.HashSet;

public class Main8OfMy_1
{
	/*
	 * 先说一下思路吧,就是将所有的全排列加入到这个set集合中,然后集合的大小就是个数
	 * 全排列的思路可以看我的博客有写
	 */
	public static HashSet<String> set=new HashSet<String>();
	public static char[] arr="LANQIAO".toCharArray();
	public static char temp;
	public static String TempString="";
	
	
	public static void main(String[] args)
	{
		kinds(arr, 0, arr.length, set);
		System.out.println(set.size());
	}
	public static void kinds(char[] arr,int start,int end,HashSet<String> set)
	{
		if(start==end)
		{
			for (char element : arr)
			{
				TempString+=element;
			}
			set.add(TempString);
			TempString="";
		}
		for (int i = start; i < end; i++)
		{
			swap(arr, i, start);
			kinds(arr, start+1, end, set);
			swap(arr, i, start);
		}
	}
	public static void swap(char[] arr,int x,int y)
	{
		temp=arr[x];
		arr[x]=arr[y];
		arr[y]=temp;
	}
}

package competition2;

import java.util.HashSet;

public class Main8OfMy_2
{
	/*
	 * 这个也是全排列的思想,就是将上面的位置交换改成了
	 * 一个数组来标识和一个临时数组来保存
	 */
	private static char[] arr="LANQIAO".toCharArray();
	private static Integer length=arr.length;
	
	private static boolean[] visit=new boolean[length];
	private static HashSet<String> set=new HashSet<String>();
	private static char[] temp=new char[length];
	public static void main(String[] args)
	{
		kinds(0);
		System.out.println(set.size());
	}
	public static void kinds(int index)
	{
		if(index==length)
		{
			set.add(new String(temp));
			return;
		}
		for(int x=0;x<length;x++)
		{
			if(!visit[x])
			{
				temp[index]=arr[x];
				visit[x]=true;
				kinds(index+1);
				visit[x]=false;
			}
		}
	}
}

package competition2;

import java.util.HashSet;

public class Main8OfMy_3
{
	/*
	 * 现在这种思路就是写一个去重复的全排列,
	 * 就是在进行交换之前,那当前位置的元素和要交换元素之间的每一个元素进行比较
	 * 如果发现相同的就跳过当前的递归,因为如果出现了第二个相同的元素再继续递归
	 * 就会得到重复的结果了,这个元素第一次出现进行递归的时候已经将后面的所有可能都
	 * 递归出来了
	 */
	public static HashSet<String> set=new HashSet<String>();
	public static char[] arr="LANQIAO".toCharArray();
	public static char temp;
	public static String TempString="";
	
	public static void main(String[] args)
	{
		kinds(arr, 0, arr.length, set);
		System.out.println(set.size());
	}
	public static void kinds(char[] arr,int start,int end,HashSet<String> set)
	{
		if(start==end)
		{
			for (char element : arr)
			{
				TempString+=element;
			}
			set.add(TempString);
			TempString="";
		}
		for (int i = start; i < end; i++)
		{
			if(IfExit(arr, start, i))
			{
				continue;
			}
			swap(arr, i, start);
			kinds(arr, start+1, end, set);
			swap(arr, i, start);
		}
	}
	public static boolean IfExit(char[] arr,int x,int y)
	{
		for(;x<y;x++)
		{
			if(arr[x]==arr[y])
			{
				return true;
			}
		}
		return false;
	}
	public static void swap(char[] arr,int x,int y)
	{
		temp=arr[x];
		arr[x]=arr[y];
		arr[y]=temp;
	}
}

第题五

题目

问题描述
  给定一个单词,请使用凯撒密码将这个单词加密。
  凯撒密码是一种替换加密的技术,单词中的所有字母都在字母表上向后偏移3位后被替换成密文。
  即a变为d,b变为e,...,w变为z,x变为a,y变为b,z变为c。
  例如,lanqiao会变成odqtldr。
输入格式
  输入一行,包含一个单词,单词中只包含小写英文字母。
输出格式
  输出一行,表示加密后的密文。
样例输入
lanqiao
样例输出
odqtldr
评测用例规模与约定
  对于所有评测用例,单词中的字母个数不超过100

答案

package competition2;

import java.util.Scanner;

public class Main1OfMy
{
	public static void main(String[] args)
	{
		Scanner in=new Scanner(System.in);
		String nextLine = in.nextLine();
		char[] temp=new char[nextLine.length()];
		for (int i = 0; i < nextLine.length(); i++)
		{
			temp[i]=judge(nextLine.charAt(i));
		}
		System.out.println(new String(temp));
		in.close();
	}
	public static char judge(char in)
	{
		if(in<'x')
		{
			return (char)(in+3);
		}
		if(in=='x')
		{
			return 'a';
		}
		if(in=='y')
		{
			return 'b';
		}
		else
		{
			return 'c';
		}
	}
}

第六题

题目

问题描述
  给定三个整数 a, b, c,如果一个整数既不是 a 的整数倍
  也不是 b 的整数倍还不是 c 的整数倍,则这个数称为反倍数。
  请问在 1 至 n 中有多少个反倍数。
输入格式
  输入的第一行包含一个整数 n。
  第二行包含三个整数 a, b, c,相邻两个数之间用一个空格分隔。
输出格式
  输出一行包含一个整数,表示答案。
样例输入
30
2 3 6
样例输出
10
样例说明
  以下这些数满足要求:1, 5, 7, 11, 13, 17, 19, 23, 25, 29。
评测用例规模与约定
  对于 40% 的评测用例,1 <= n <= 10000。
  对于 80% 的评测用例,1 <= n <= 100000。
  对于所有评测用例,1 <= n <= 10000001 <= a <= n,1 <= b <= n,1 <= c <= n。

答案

package competition2;

import java.util.Scanner;

public class Main2
{
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		int a,b,c;
		a=in.nextInt();b=in.nextInt();c=in.nextInt();
		in.close();
		int count=0;
		for(int x=1;x<=n;x++)
		{
//			if(x%a!=0&&x%b!=0&&x%c!=0)
//			{
//				count++;
//			}
			if(x%a==0)
			{
				continue;
			}
			if(x%b==0)
			{
				continue;
			}
			if(x%c==0)
			{
				continue;
			}
			count++;
		}
		System.out.println(count);
	}
}

第七题

题目

问题描述
  对于一个 n 行 m 列的表格,我们可以使用螺旋的方式给表格依次填上正整数,
  我们称填好的表格为一个螺旋矩阵。
  例如,一个 45 列的螺旋矩阵如下:
  1 2 3 4 5
  14 15 16 17 6
  13 20 19 18 7
  12 11 10 9 8
输入格式
  输入的第一行包含两个整数 n, m,分别表示螺旋矩阵的行数和列数。
  第二行包含两个整数 r, c,表示要求的行号和列号。
输出格式
  输出一个整数,表示螺旋矩阵中第 r 行第 c 列的元素的值。
样例输入
4 5
2 2
样例输出
15
评测用例规模与约定
  对于 30% 的评测用例,2 <= n, m <= 20。
  对于 70% 的评测用例,2 <= n, m <= 100。
  对于所有评测用例,2 <= n, m <= 10001 <= r <= n,1 <= c <= m。


答案

package competition2;

import java.util.Scanner;

public class Main3OfMy_1
{
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();
		int r = sc.nextInt();
		int c = sc.nextInt();
		int[][] arr=new int[n][m];
		sc.close();
		
		int num=1;
		int up=0,down=n,right=m,left=0;
		while(true)
		{
			for(int col=left;col<right;col++)
			{
				arr[up][col]=num++;
			}
			up++;
			if(up==down)
			{
				break;
			}
			for(int row=up;row<down;row++)
			{
				arr[row][right-1]=num++;
			}
			right--;
			if(right==left)
			{
				break;
			}
			for(int col=right-1;col>=left;col--)
			{
				arr[down-1][col]=num++;
			}
			down--;
			if(down==up)
			{
				break;
			}
			for(int row=down-1;row>=up;row--)
			{
				arr[row][left]=num++;
			}
			left++;
			if(left==right)
			{
				break;
			}
		}
		System.out.println(arr[r-1][c-1]);
	}
}

第八题

题目

问题描述
  如果一个序列的奇数项都比前一项大,偶数项都比前一项小,
  则称为一个摆动序列。即 a[2i]<a[2i-1], a[2i+1]>a[2i]。
  小明想知道,长度为 m,每个数都是 1 到 n 之间的正整数的摆动序列一共有多少个。
输入格式
  输入一行包含两个整数 m,n。
输出格式
  输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
样例输入
3 4
样例输出
14
样例说明
  以下是符合要求的摆动序列:
  2 1 2
  2 1 3
  2 1 4
  3 1 2
  3 1 3
  3 1 4
  3 2 3
  3 2 4
  4 1 2
  4 1 3
  4 1 4
  4 2 3
  4 2 4
  4 3 4
评测用例规模与约定
  对于 20% 的评测用例,1 <= n, m <= 5;
  对于 50% 的评测用例,1 <= n, m <= 10;
  对于 80% 的评测用例,1 <= n, m <= 100;
  对于所有评测用例,1 <= n, m <= 1000

答案

递归
package competition2;

import java.util.Scanner;

public class Main4
{
	/*
	 * 首先你可能想到的是完全的递归,但是这样子递归算法复杂度接近指数级别的
	 * 看看评测用例规模与约定的数据规模,完全是不够用的
	 */
	public static int m,n,count=0;
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		m=in.nextInt();
		n=in.nextInt();
		in.close();
		for(int x=2;x<=n;x++)
		{
			start(2, x, 1);
		}
		System.out.println(count);
	}
	/**
	 * @param index		代表当前的位置,可以用来判断当前位置的奇偶性
	 * @param before	代表当前位置的前一个数是什么
	 * @param now		代表当前位置的这个数是什么
	 */
	public static void start(int index,int before,int now)
	{
		if(index==m)
		{				
			count=(count+1)%10000;
		}
		if(index%2==0)
		{
			if(now+1<before)
			{
				start(index, before, now+1);
			}
			if(index<m)
			{
				start(index+1, now, now+1);
			}
		}
		else
		{
			if(now<n)
			{
				start(index, before, now+1);
			}
			if(index<m)
			{
				start(index+1, now, 1);
			}
		}
	}
}

优化
package competition2;

import java.util.Scanner;

public class Main4OfMy_1
{
	/*
	 * 首先还是一样,第一个位置可以选择的是2-n
	 * 到后面的规律就是:
	 * 如果是奇数位置选择了i,下一个是偶数位置,可以选择的是1到i-1
	 * 如果是偶数位置选择了i,下一个是奇数位置,可以选择的是i+1到n
	 * 而且像上面的递归包含了大量的重复的子问题,
	 * 所以可以使用记忆型的递归模型
	 * 递归式子是:
	 * dfs(i,k)=求和{dfs(temp,k+1)}		k是奇数,temp是1到i-1
	 * dfs(i,k)=求和{dfs(temp,k+1)}		k是偶数,temp是i+1到n
	 * 对于 80% 的评测用例,1 <= n, m <= 100;
	 * 但是这样算法复杂度是接近O(n^3),所以只能通过80%的数据
	 */
	public static int[][] arr=new int[1000][1000];
	public static int m,n,mod=10000;
	
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		m=sc.nextInt();
		n=sc.nextInt();
		int count=0;
		for(int x=2;x<=n;x++)
		{
			count =(count+dfs(x, 1))%mod;
		}
		System.out.println(count);
	}
	public static int dfs(int i,int k)
	{
		if(k==m)
		{
			return 1;
		}
		if(arr[i][k]!=0)
		{
			return arr[i][k];
		}
		//如果是奇数的话
		if((k&1)==1)
		{
			for(int x =1;x<i;x++)
			{
				arr[i][k]=(arr[i][k]+dfs(x, k+1))%mod;
			}
		}
		else
		{
			for(int x=i+1;x<=n;x++)
			{
				arr[i][k]=(arr[i][k]+dfs(x, k+1))%mod;
			}
		}
		return arr[i][k];
	}
}

优化
package competition2;

import java.util.Scanner;

public class Main4OfMy_2
{
	/*
	 * 又来写老套路了,上面的算法有百分之二十的数据是无法通过的,那如何是好呀?
	 * 这种需要递归的,而且又有很多重复的子问题的,可以进行记忆型递归
	 * 还有一种更加优化的思路:这种递归中有加总的样子,可以修改递归式子来进行优化
	 * 想尽办法将递归式子变成那种集合的意义.
	 * 
	 * 上面中dfs(i,k)的意义是第k个位置填写i的时候有多少种可能
	 * 现在改编成集合的意义,
	 * dfs(i,k)的意义是:
	 * 当k是奇数的时候,第k个位置中可以填写i+1到n,填写i+1到n一共有多少种可能
	 * 当k是偶数的时候,第k个位置中可以填写1到i-1,填写1到i-1一共有多少种可能
	 * 那么对应的递归方程式子如何进行拆分呢?
	 * k是奇数的时候:
	 * 		dfs(i,k)=dfs(i-1,k+1)+dfs(i+1,k)
	 * k是偶数的时候:
	 * 		dfs(i,k)=dfs(i+1,k+1)+dfs(i-1,k)
	 * 那么是如何意思呢?
	 * 比如题目给出的例子,m=3,n=4
	 * dfs(2,1)表示第一个位置可以填2,3,4,
	 * dfs(2,1)=dfs(3,1)+dfs(1,2)
	 * dfs(3,1)=dfs(4,1)+dfs(2,2)
	 * dfs(1,2)=dfs(0,2)+dfs(2,3)
	 * ......对应下面的
	 * dfs(2,1)=dfs(3,1)+dfs(1,2)分成两部分
	 * 			2 1 ...
	 * 和
	 * 			3 1 ...
	 * 			3 2 ...
	 * 			4 1 ...
	 * 			4 2 ...
	 * 			4 3 ...
	 * 就是将开头是2的和开头不是2的两部分
	 */
	public static int m,n,mod=10000;
	public static int[][] arr=new int[1000][1000];
			
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		m=sc.nextInt();
		n=sc.nextInt();
		sc.close();
		int count = dfs(2, 1);
		System.out.println(count);
	}
	public static int dfs(int i,int k)
	{
		if(i<1 ||i>n)
		{
			return 0;
		}
		if(k==m)
		{
			if((k&1)==1)
			{
				arr[i][k]=n-i+1;
			}
			else
			{
				arr[i][k]=i;
			}
		}
		if(arr[i][k]!=0)
		{
			return arr[i][k];
		}
		if((k&1)==1)
		{
			arr[i][k]=(arr[i][k]+dfs(i+1, k)+dfs(i-1, k+1))%mod;
		}
		else
		{
			arr[i][k]=(arr[i][k]+dfs(i-1, k)+dfs(i+1, k+1))%mod;
		}
		return arr[i][k];
	}
}

最后优化
package competition2;

import java.util.Scanner;

public class Main4OfMy_3
{
	/*
	 * 上面的那个优化后的递归的方法,因为递归的层次太深了,会超出栈空间的
	 * 所以我们得继续优化,将递归改成递推,而递推就是递归的逆过程
	 * 比如我们看上面递归的函数的出口,从而初始化递推的数组,
	 * 然后按照递归的逆过程来生成递推的数组
	 * 
	 * 递归的出口:
	 * 				if(k==m)
					{
						if((k&1)==1)
						{
							arr[i][k]=n-i+1;
						}
						else
						{
							arr[i][k]=i;
						}
					}
		对应的递推的初始化递推数组:
					for(int x=n;x>0;x--)
					{
						if((m&1)==1)
						{
							arr[x][m]=n-x+1;
						}
						else
						{
							arr[x][m]=x;
						}
					}
		然后是递归的逆过程进行递推,奇数列就逆着递推,偶数列就顺着递推
		为什么呢?
		因为奇数列递推需要这个列的靠后一个数据
		而偶数列递推需要靠前的一共数据
	 */
	public static int m,n;
	public static int[][] arr;
	public static int mod=10000;
	
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		m=sc.nextInt();
		n=sc.nextInt();
		arr=new int[n+1][m+1];
		sc.close();
		
		//初始化数组
		for(int x=n;x>0;x--)
		{
			if((m&1)==1)
			{
				arr[x][m]=n-x+1;
			}
			else
			{
				arr[x][m]=x;
			}
		}
		//然后进行递归的逆过程
		for(int y=m-1;y>0;y--)
		{
			//如果是奇数
			if((y&1)==1)
			{
				for(int x=n;x>0;x--)
				{
					arr[x][y]=((x==n ? 0: arr[x+1][y])+arr[x-1][y+1])%mod;
				}
			}
			else
			{
				for(int x=1;x<=n;x++)
				{
					arr[x][y]=(arr[x-1][y]+(x==n?0:arr[x+1][y+1]))%mod;
				}
			}
		}
		System.out.println(arr[2][1]);
//		输出数组看一下
//		for(int x=0;x<5;x++)
//		{
//			for(int y=0;y<4;y++)
//			{
//				System.out.print(arr[x][y]+"	");
//			}
//			System.out.println();
//		}
	}
}

第九题

题目

问题描述
  小明和朋友们一起去郊外植树,他们带了一些在自己实验室精心研究出的小树苗。
  小明和朋友们一共有 n 个人,他们经过精心挑选,在一块空地上每个人挑选了一个适合植树的位置,
  总共 n 个。他们准备把自己带的树苗都植下去。
  
  然而,他们遇到了一个困难:有的树苗比较大,而有的位置挨太近,导致两棵树植下去后会撞在一起。
  他们将树看成一个圆,圆心在他们找的位置上。如果两棵树对应的圆相交,
  这两棵树就不适合同时植下(相切不受影响),称为两棵树冲突。
  
  小明和朋友们决定先合计合计,只将其中的一部分树植下去,保证没有互相冲突的树。
  他们同时希望这些树所能覆盖的面积和(圆面积和)最大。
输入格式
  输入的第一行包含一个整数 n ,表示人数,即准备植树的位置数。
  接下来 n 行,每行三个整数 x, y, r,表示一棵树在空地上的横、纵坐标和半径。
输出格式
  输出一行包含一个整数,表示在不冲突下可以植树的面积和。
  由于每棵树的面积都是圆周率的整数倍,请输出答案除以圆周率后的值(应当是一个整数)。
样例输入
6
1 1 2
1 4 2
1 7 2
4 1 2
4 4 2
4 7 2
样例输出
12
评测用例规模与约定
  对于 30% 的评测用例,1 <= n <= 10;
  对于 60% 的评测用例,1 <= n <= 20;
  对于所有评测用例,1 <= n <= 300 <= x, y <= 10001 <= r <= 1000

答案

package competition2;

import java.util.Scanner;

public class Main5OfMy_1
{
	/*
	 * 每棵树可以选也可以不选,但不知道哪种决策结果最大,
	 * 只能先考虑暴力搜索每种情况,总可选数为2的n次方的深度优先搜索。
	 * 下面area表示树占用的面积,遍历每一种情况,将最大的面积赋值给area
	 * trees是保存树的数组,arr是保存树和树之间有没有冲突的情况
	 */
	public static int area=0;
	public static int n;
	public static MyTree[] trees;
	public static int [][] arr;
	
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		n=in.nextInt();
		trees=new MyTree[n];
		for(int x=0;x<n;x++)
		{
			trees[x]=new MyTree(in.nextInt(), in.nextInt(), in.nextInt());
		}
		
		arr=new int[n][n];
		for(int x=0;x<n-1;x++)
		{
			for(int y=x+1;y<n;y++)
			{
				if(trees[x].IsConflict(trees[y]))
				{
					arr[x][y]=1;
					arr[y][x]=1;
				}
			}
		}
		//上面都是将数组准备好
		dfs(0, 0);
		System.out.println(area);
		in.close();
	}
	//这里index代表想加入第几棵树,sum代表加入到index-1棵树最大的占用面积是多少
	public static void dfs(int sum,int index)
	{
		//当遍历到最后一棵树后就直接计算总面积
		if(index==n)
		{
			area=sum>area?sum:area;
			return;
		}
		//先看下index这棵树能不能种下去
		boolean IsOk=true;
		for(int x=0;x<n;x++)
		{
			if(trees[x].Isplant && arr[x][index]==1)
			{
				IsOk=false;
				break;
			}
		}
		//情况1:如果可以种下去就选择这棵树
		if(IsOk)
		{
			trees[index].Isplant=true;
			int rr=trees[index].r*trees[index].r;
			dfs(sum+rr, index+1);
			trees[index].Isplant=false;		//回溯
		}
		//情况2:不选这棵树
		trees[index].Isplant=false;
		dfs(sum, index+1);
	}
}
class MyTree
{
	int x;
	int y;
	int r;
	boolean Isplant;
	public MyTree(int x, int y, int r)
	{
		this.x = x;
		this.y = y;
		this.r = r;
	}
	public boolean IsConflict(MyTree myTree)
	{
		if((x-myTree.x)*(x-myTree.x)+(y-myTree.y)*(y-myTree.y)<(r+myTree.r)*(r+myTree.r))
		{
			return true;
		}
		return false;
	}
}

优化

package competition2;

import java.util.Arrays;
import java.util.Scanner;

public class Main5OfMy_2
{
	/*
	 * 优化的关键点在于用类似贪心的办法(但不是贪心):
	 * 将圆按半径从大到小排序,这样优先考虑半径大的圆的选与不选问题;
	 * 另外把“选”这个分支放在“不选”这个分支前面执行,这样我们相信会尽早地遇到最优解。
	 * 
	 * 基于这个假设,在递归之前我们可以以O(N)的复杂度存储所有圆的“半径的平方”的后缀和,
	 * 计为数组s;在递归函数dfs中,参数sum代表index之前的选择策略所得到的sum,
	 * s[index]代表包括index索引及之后续所有圆的半径的平方和,
	 * 如果sum+s[index]小于等于已经求得的ans,那就不必进行任何后续的选择试探了,可立即退出递归。
	 */
	/*
	 * 每棵树可以选也可以不选,但不知道哪种决策结果最大,
	 * 只能先考虑暴力搜索每种情况,总可选数为2的n次方的深度优先搜索。
	 * 下面area表示树占用的面积,遍历每一种情况,将最大的面积赋值给area
	 * trees是保存树的数组,arr是保存树和树之间有没有冲突的情况
	 */
	public static int area=0;
	public static int n;
	public static MyTree1[] trees;
	public static int [][] arr;
	//半径平方后缀和
	public static int[] suffix;
	
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		n=in.nextInt();
		trees=new MyTree1[n];
		for(int x=0;x<n;x++)
		{
			trees[x]=new MyTree1(in.nextInt(), in.nextInt(), in.nextInt());
		}
		
		arr=new int[n][n];
		for(int x=0;x<n-1;x++)
		{
			for(int y=x+1;y<n;y++)
			{
				if(trees[x].IsConflict(trees[y]))
				{
					arr[x][y]=1;
					arr[y][x]=1;
				}
			}
		}
		//初始化后缀和排序树数组
		Arrays.sort(trees);
		suffix=new int[n];
		suffix[n-1]=trees[n-1].r*trees[n-1].r;
		for(int x=n-1-1;x>=0;x--)
		{
			suffix[x]=suffix[x+1]+trees[x].r*trees[x].r;
		}
		
		//上面都是将数组准备好
		dfs(0, 0);
		System.out.println(area);
		in.close();
	}
	//这里index代表想加入第几棵树,sum代表加入到index-1棵树最大的占用面积是多少
	public static void dfs(int sum,int index)
	{
		//当遍历到最后一棵树后就直接计算总面积
		if(index==n)
		{
			area=sum>area?sum:area;
			return;
		}
		//修改的地方来了,如果index之前的sum加上自index开始的半径的平方和小于ans,则没必要继续
		if(sum+suffix[index]<=area)
		{
			return;
		}
		
		//先看下index这棵树能不能种下去
		boolean IsOk=true;
		for(int x=0;x<n;x++)
		{
			if(trees[x].Isplant && arr[x][index]==1)
			{
				IsOk=false;
				break;
			}
		}
		//情况1:如果可以种下去就选择这棵树
		if(IsOk)
		{
			trees[index].Isplant=true;
			int rr=trees[index].r*trees[index].r;
			dfs(sum+rr, index+1);
			trees[index].Isplant=false;		//回溯
		}
		//情况2:不选这棵树
		trees[index].Isplant=false;
		dfs(sum, index+1);
	}
}
class MyTree1 implements Comparable<MyTree1>
{
	int x;
	int y;
	int r;
	boolean Isplant;
	public MyTree1(int x, int y, int r)
	{
		this.x = x;
		this.y = y;
		this.r = r;
	}
	public boolean IsConflict(MyTree1 MyTree1)
	{
		if((x-MyTree1.x)*(x-MyTree1.x)+(y-MyTree1.y)*(y-MyTree1.y)<(r+MyTree1.r)*(r+MyTree1.r))
		{
			return true;
		}
		return false;
	}
	@Override
	public int compareTo(MyTree1 o)
	{
		return o.r-this.r;//注意这里是要降序的
	}
}

第十题

题目

问题描述
  2015年,全中国实现了户户通电。作为一名电力建设者,小明正在帮助一带一路上的国家通电。
  这一次,小明要帮助 n 个村庄通电,其中 1 号村庄正好可以建立一个发电站,所发的电足够所有村庄使用。
  现在,这 n 个村庄之间都没有电线相连,小明主要要做的是架设电线连接这些村庄,
  使得所有村庄都直接或间接的与发电站相通。
  
  小明测量了所有村庄的位置(坐标)和高度,如果要连接两个村庄,
  小明需要花费两个村庄之间的坐标距离加上高度差的平方,
  形式化描述为坐标为 (x_1, y_1) 高度为 h_1 的村庄与坐标为 (x_2, y_2) 
  高度为 h_2 的村庄之间连接的费用为
  sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2))+(h_1-h_2)*(h_1-h_2)。
  在上式中 sqrt 表示取括号内的平方根。请注意括号的位置,高度的计算方式与横纵坐标的计算方式不同。
  由于经费有限,请帮助小明计算他至少要花费多少费用才能使这 n 个村庄都通电。
输入格式
  输入的第一行包含一个整数 n ,表示村庄的数量。
  接下来 n 行,每个三个整数 x, y, h,分别表示一个村庄的横、纵坐标和高度,
  其中第一个村庄可以建立发电站。
输出格式
  输出一行,包含一个实数,四舍五入保留 2 位小数,表示答案。
样例输入
4
1 1 3
9 9 7
8 8 6
4 5 4
样例输出
17.41
评测用例规模与约定
  对于 30% 的评测用例,1 <= n <= 10;
  对于 60% 的评测用例,1 <= n <= 100;
  对于所有评测用例,1 <= n <= 10000 <= x, y, h <= 10000

答案

这就是直接问最小生成树的问题了,连通就是相当于树,还要代价最小就是最小生成树
只不过这里的评测用例中n的数量级达到1000,那么就只能用Kruskal算法了,因为prim算法的时间复杂度是n^3,会运行超时的

package competition2;

import java.util.Scanner;

public class Main6
{
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		int nodes=in.nextInt();
		//表示每两个村庄直接的距离,这里对应通电的费用
		double[][] weight=new double[nodes][nodes];
		//表示每一个村庄的位置
		int[][] location=new int[nodes][3];
		//表示有多少条边
		int EdgeNumber=0;
		//下面初始化村庄位置和费用数组
		for(int i=0;i<nodes;i++)
		{
			location[i][0]=in.nextInt();
			location[i][1]=in.nextInt();
			location[i][2]=in.nextInt();
		}
		for(int x=0;x<weight.length;x++)
		{
			for(int y=x+1;y<weight.length;y++)
			{
				weight[x][y]=Math.sqrt(
						 Math.pow((location[x][0]-location[y][0]),2)+
						 Math.pow((location[x][1]-location[y][1]),2))+
						 Math.pow((location[x][2]-location[y][2]),2);
				EdgeNumber++;
			}
		}
		//用来存储边的数组,并且初始化
		Edge[] edges=new Edge[EdgeNumber];
		int index=0;
		for(int x=0;x<weight.length;x++)
		{
			for(int y=x+1;y<weight[x].length;y++)
			{
				edges[index++]=new Edge(x, y, weight[x][y]);
			}
		}
		//然后对边进行排序
		for(int x=0;x<edges.length-1;x++)
		{
			for(int y=0;y<edges.length-x-1;y++)
			{
				if(edges[y].weight>edges[y+1].weight)
				{
					Edge temp=edges[y];
					edges[y]=edges[y+1];
					edges[y+1]=temp;
				}
			}
		}
		double count=0;
		//用来表示每一个节点的顶点是谁
		int[] ends=new int[nodes];
		//然后遍历每一条边,看看是否需要加入到最小生成树里面,
		//然后我们的需求还挺简单的,就只需要最后一个费用,
		//所以我们就只需要保存加入最小生成树的边的权重和就行
		for(int x=0;x<EdgeNumber;x++)
		{
			//然后想得到这条边的两个节点的顶点是否是同一个顶点,不是同一个就加入
			int end1=edges[x].start;
			int end2=edges[x].end;
			while(ends[end1]!=0)
			{
				end1=ends[end1];
			}
			while(ends[end2]!=0)
			{
				end2=ends[end2];
			}
			if(end1!=end2)
			{
				ends[end1]=end2;
				count +=edges[x].weight;
			}
		}
		System.out.printf("%.2f",count);
	}
}
class Edge
{
	int start;
	int end;
	double weight;
	public Edge(int start, int end, double weight)
	{
		this.start = start;
		this.end = end;
		this.weight = weight;
	}
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReflectMirroring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值