十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第1张图片

package com.lanqiao.十一届省赛第二场Java大学A组;
//正确。
public class Class_01门牌制作 {

	public static void main(String[] args) {
		int ans=0;
		//暴力枚举
		for (int i = 1; i <=2020; i++) {
			String str=i+"";
			for (int j = 0; j < str.length(); j++) {
				if (str.charAt(j)=='2') {
					ans++;
				}
			}
		}
		System.out.println(ans);//624
	}
}

运行结果:

624

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第2张图片

package com.lanqiao.十一届省赛第二场Java大学A组;

//正确
public class Class_02既约分数 {

	public static void main(String[] args) {
		int ans=0;
		//枚举暴力
		for (int i = 1; i <=2020; i++) {//分子
			for (int j = 1; j <=2020; j++) {//分母
				if (gcd(i, j)==1) {
					ans++;
				}
			}
		}
		System.out.println(ans);//2481215,正确
	}
	
	static int gcd(int a,int b) {//求a和b的最大公约数
		while(b!=0) {
			int t=a%b;
			a=b;
			b=t;
		}
		return a;
	}
}

运行结果:

2481215

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第3张图片

package com.lanqiao.十一届省赛第二场Java大学A组;
//结果正确
public class Class_03蛇形填数 {

	public static void main(String[] args) {
		//1,(1,1)
		//5,(2,2)
		//13,(3,3)
		//25,(4,4):(1+4X1+4X2+4X3=25)
		//...
		//x,(20,20):1+4X(1+2+3+...+19)=761
	}
}

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第4张图片十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第5张图片

package com.lanqiao.十一届省赛第二场Java大学A组;

import java.text.DateFormatSymbols;
import java.util.zip.Inflater;

//有80种
//枚举出了所有结果:
//https://blog.csdn.net/m0_46272108/article/details/109157960?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=1328769.60809.16176281975574329&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
//编程解决:
//dfs+并查集   https://blog.csdn.net/qq_45530271/article/details/109189978
public class Class_04七段码 {

	//基本思路:dfs搜索所有的状态,判断每种状态可不可行。判断的方法是把每个灯管当做一个节点,编号,连边建图,对搜索
	//出的亮灯方案使用并查集判断亮的灯管是否在同一个集合中
	
	static int N=10;
	static int[] use=new int[N];
	static int ans;
	static int[][] e=new int[N][N];
	static int[] fa=new int[N];
	
	static void init() {
		/*
		 * 	连边建图,e[i][j]==1表示i和j相邻
		 * a b c d e f g
		 * 1 2 3 4 5 6 7  邻接矩阵
		 */
		e[1][2]=e[1][6]=1;
		e[2][1]=e[2][7]=e[2][3]=1;
		e[3][2]=e[3][7]=e[3][4]=1;
		e[4][3]=e[4][5]=1;
		e[5][4]=e[5][6]=e[5][7]=1;
		e[6][1]=e[6][7]=e[6][5]=1;
		e[7][2]=e[7][3]=e[7][6]=e[7][5]=1;	
	}
	
	static int find(int u) {//查找u的根节点,即集合编号
		if (fa[u]==u) {
			return u;
		}
		return fa[u]=find(fa[u]);//路径压缩,并查集的查找其根节点
	}
	
	//深度遍历
	static void dfs(int d) {
		if (d>7) {
			//并查集判断是否在同一个集合中
			for (int i = 1; i <= 7; i++) {//初始化父亲集合,并查集的初始化
				fa[i]=i;
			}
			for (int i = 1; i <= 7; i++) {
				for (int j = 1; j <= 7; j++) {
					if (e[i][j]==1&&use[i]==1&&use[j]==1) {
						int fx=find(i);
						int fy=find(j);
						if (fx!=fy) {//并查集的合并,这里是简单处理,一般树高度低的并查集作为高度高的树的孩子
							fa[fx]=fy;//如果不在同一集合,合并
						}
					}
				}
			}
			
			int k=0;
			for (int i = 1; i <= 7; i++) {
				//比如七个灯用啦,只有一个灯代表整个集合的编号,即根节点
				//只有一个根节点fa[i]==i
				if (use[i]==1&&fa[i]==i) {
					k++;
				}
			}
			if (k==1) {
				ans++;//如果所有亮灯都属于同一个集合
			}
			return;
		}
		
		use[d]=1;//打开d这个灯,继续开关下一个灯
		dfs(d+1);
		use[d]=0;//关闭d这个灯,继续开关下一个灯
		dfs(d+1);
	}

	public static void main(String[] args) {
		init();
		dfs(1);
		System.out.println(ans);//80
		
	}
}

运行结果:

80

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第6张图片

package com.lanqiao.十一届省赛第二场Java大学A组;

//关于平面分割的问题请先参考博客:
//https://blog.csdn.net/stumiss/article/details/115417620?spm=1001.2014.3001.5502
public class Class_05平面分割 {

	public static void main(String[] args) {
		//1391这个结果是对的
		System.out.println(fun1(20));//211对的
		System.out.println(fun2(20));//最终结果:1401//这个错啦。
	}
	
	/**
	 * n-1条直线区域数为f(n-1),第n条直线与前面n-1个直线相交且交点不是同一个
	 * f(n)=f(n-1)+n
	 */
	static int fun1(int x) {
		if (x==1) {
			return 2;
		}else {
			return fun1(x-1)+x;
		}
	}
	
	/**
	 * 20条直线区域数最多为211,第n-1个圆与20条直线相交,区域数最多是f(n-1)
	 * 	则第n个圆不仅与20条直线相交,还要与前面的n-1个圆相交。第n个圆被分成多少个
	 * 	线段:20*2+2*(n-1)
	 * 	所以f(n)=f(n-1)+2*n+38
	 * 
	 */
	static int fun2(int x) {
		if (x==1) {//20条直线,一个圆,此圆被分为40个线段,这些线段将已有区域一分为2,在原来区域的基础上新增40个区域,211+40=251.
			//return 261;原来这个地方搞错啦
			return 251;
		}else {
			return fun2(x-1)+2*x+38;
		}
	}
}

运行结果:

211
1391   //这个是本题的最终结果

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第7张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第8张图片十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第9张图片

package com.lanqiao.十一届省赛第二场Java大学A组;

import java.util.Scanner;

public class Class_06成绩分析 {

	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();
		int[] arr=new int[n];
		for (int i = 0; i < arr.length; i++) {
			arr[i]=scanner.nextInt();
		}
		
		int max=arr[0];
		int min=arr[0];
		int avg;
		double ans=0;
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]>max) {
				max=arr[i];
			}
			if (arr[i]<min) {
				min=arr[i];
			}
			ans+=arr[i];
		}
		
		System.out.println(max);
		System.out.println(min);
		System.out.printf("%.2f",ans/n);//四舍五入保留两位小数,ans为double类型
//		System.out.println(Math.round(ans/n*100)/100);
	}
}

运行结果:

3    //3个人,1分,1分,0分
1
1
0
1
0
0.67

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第10张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第11张图片
在这里插入图片描述
这里提供两种思路的代码:
1) 暴力枚举每个日子,判断其是否回文日期及是否符合ABABBABA型.
代码如下:

package com.lanqiao.十一届省赛第二场Java大学A组;

import java.util.Scanner;

public class Class_07回文日期 {

	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		//20201231
		String str=scanner.next();
		
		//枚举
		int year=Integer.parseInt(str.substring(0, 4));
		int month=Integer.parseInt(str.substring(4,6));
		int day=Integer.parseInt(str.substring(6));
		
		String temp="";
		String temp2="";
		
//		long start=System.currentTimeMillis();
		
		//暴力枚举每一天,判断其是否是回文日期和ABAB型的回文日期
		loop1:
		for (int i = year; i <= 8999; i++) {
			
			for (int j = (i==year?month:1); j <=12; j++) {
				int day2;
				if (j==1||j==3||j==5||j==7||j==8||j==10||j==12) {
					day2=31;
				}else if(j==4||j==6||j==9||j==11) {
					day2=30;
				}else {
					day2=isRunNian(year)?29:28;
				}
				
				//从输入日期的后一天开始算,此后循环就从每年的1月开始到12月,每月的1日到月末
				for (int j2 =(j==month&&i==year?day+1:1) ; j2 <=day2; j2++) {
					StringBuilder sb=new StringBuilder();
					sb.append(i+"");
					if (j<10) {
						sb.append(0+"");
					}
					sb.append(j+"");
					if (j2<10) {
						sb.append(0+"");
					}
					sb.append(j2+"");
//					StringBuilder s=sb.reverse();
//					sb.reverse();
//					String str1=new String(sb);
//					String str2=new String(s);
					//sb和s似乎共地址,sb变化,s跟着变化。
					if ((sb.toString()).equals(sb.reverse().toString())) {
						temp=sb.toString();
						break loop1;//退出loop1循环。
					}
				}
			}
		}
		
		System.out.println(temp);
		
		//枚举每一天
		loop2:
		for (int i = year; i <= 8999; i++) {
			for (int j = (i==year?month:1); j <=12; j++) {
				int day2;
				if (j==1||j==3||j==5||j==7||j==8||j==10||j==12) {
					day2=31;
				}else if(j==4||j==6||j==9||j==11) {
					day2=30;
				}else {
					day2=isRunNian(year)?29:28;
				}
				
				for (int j2 =(j==month&&i==year?day+1:1) ; j2 <=day2; j2++) {
					StringBuilder sb=new StringBuilder();
					sb.append(i+"");
					if (j<10) {
						sb.append(0+"");
					}
					sb.append(j+"");
					if (j2<10) {
						sb.append(0+"");
					}
					sb.append(j2+"");
//							StringBuilder s=sb.reverse();
//							sb.reverse();
//							String str1=new String(sb);
//							String str2=new String(s);
					//sb和s似乎共地址,sb变化,s跟着变化。
					if ((sb.toString()).equals(sb.reverse().toString())) {
						temp=sb.toString();
						if (temp.charAt(0)==temp.charAt(2)
								&&temp.charAt(0)==temp.charAt(5)
								&&temp.charAt(0)==temp.charAt(7)
								&&temp.charAt(1)==temp.charAt(3)
								&&temp.charAt(1)==temp.charAt(4)
								&&temp.charAt(1)==temp.charAt(6)) {
							temp2=sb.toString();//ABABBABA型
							break loop2;
						}
						
					}
				}
			}
		}
		System.out.println(temp2);
//		long end=System.currentTimeMillis();
//		System.out.println(end-start);
	}
	
	//判断闰年,返回true,是闰年,反之不是。
	static boolean isRunNian(int year) {
		boolean flag=false;//表示不是闰年
		if (year%400==0) {
			flag=true;
		}
		if(year%4==0&&year%100!=0) {
			flag=true;
		}
		return flag;
	}
}

测试用例如下:

20200202
20211202
21211212
10000101
10011001
10100101
19800202
20011002
20200202

2)枚举每个年份:从用户输入的年份开始,把年份截取出来倒过来再拼接上形成年月日,再来判断日期是否合法和是否符合ABAB型

package com.lanqiao.十一届省赛第二场Java大学A组;

import java.text.SimpleDateFormat;
import java.util.Scanner;


/**
 * 
 * @Description 思路:截取用户输入的年份,把年份倒过来再拼接上形成年月日,再来判断
 * 	日期是否合法和是否符合ABAB型
 * 	参考博客:https://blog.csdn.net/Miraitowa_0915/article/details/114783404
 * @author mike 
 * @version
 * @date 2021-4-414:22:07
 */
public class Class_07回文日期改进 {

	
	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();
		String num=Integer.toString(n);
		
		boolean condition1=false;//用来标记普通回文数是否找到
		boolean condition2=false;//用来标记ABAB型回文数是否找到
		
		//循环一次加一年如:10000202+10000=10010202
		for (int i = n; !condition1||!condition2; i+=10000) {
			String date=Integer.toString(i);//把数字转为字符串
			//截取字符串前半部分也就是年份字符,再做反转接在后面形成回文
			StringBuffer sb=new StringBuffer(date.substring(0, 4));
			String sb2=sb.reverse().toString();
			//sb反转啦,还要再反转回来再拼接sb2
			String string=sb.reverse().toString().concat(sb2);
			
			//判断回文是否合法
			//string.compareTo(num)>0,显示的回文日期要比输入的日期大。
			//字符串比较含义,长度相同时,比较每个索引上的字符Unicode value
			if (!condition1&&isDate(string)&&string.compareTo(num)>0) {
				System.out.println(string);
				condition1=true;//找到一个回文日期,后面循环不会执行这个if分支
			}
			//加上一个判断ABAB型
			if (!condition2&&isDate(string)&&string.compareTo(num)>0
					&&string.substring(0, 2).equals(string.substring(2, 4))) {
				System.out.println(string);
				condition2=true;//找到一个ABAB型的回文日期。自此之后循环条件为false,
				//结束for循环
			}
			
		}
		scanner.close();
	}
	
	static boolean isDate(String date) {
		String dt=date.substring(0,4)+"-"+date.substring(4,6)+"-"
				+date.substring(6,8);
		//日期格式化
		SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
		try {
			//设为false能严格验证日期
			//否则SimpleDateFormat会比较宽松地验证日期,比如2007/02/29会
			//被接受,并转换成2007/03/01
			//lenient:宽大的,仁慈的
			//lenient when true, parsing is lenient
			sdf.setLenient(false);
			//解析生成日期,不合法抛出异常
			sdf.parse(dt);
		}catch (Exception e) {
			return false;//抛出异常说明日期不合法
		}
		return true;
	}
}

测试用例如下:

20200202
20211202
21211212
10000101
10011001
10100101
19800202
20011002
20200202

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第12张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第13张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第14张图片
代码如下:

package com.lanqiao.十一届省赛第二场Java大学A组;

import java.util.Scanner;

public class Class_08子串分值 {

	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		String str=scanner.nextLine();
		
		System.out.println(sum1(str));
		System.out.println(sum2(str));
	}
	
	//改进以提高效率
	//原来枚举是一行行的看,枚举每个子串,现在按列来看每个索引处的字符,以某个索引x为例,比如此索引处字符为
	//a,则可以这样理解,在所有包含此字符的子串中,有多少个子串中此字符a恰好出现一次
	//遍历输入字符串,每个索引的字符如上理解。则本题结果即为所有符合要求的子串
	//数目的和(这里理解会比较困难,看题干样例说明)
	//问题关键是如何确定子串的数目?(索引x处的a字符举例来说)
	//      pre[x]   x      next[x]
	//   .....a......a........a.....
	//pre[x]为前一个a的索引,next[x]为后一个a的索引,由于子串s[i...j]中字符a
	//恰好出现一次,则pre[x]
	//(x-pre[x])*(next[x]-x),
	//x索引处的a前后都有a为一般情况
	//考虑特殊的情况
	//(1)x索引处的a前面没有a字符出现,此时pre[x]=-1   (i可以取[0,x]的范围)
	//(2)x索引处的a后面没有a字符出现,此时next[x]=str.length()   (j可以取[x,str.length()-1]的范围)
	static int sum2(String str) {
		int len=str.length();
		
		//两个关键的数组如何初始化?
		
		int[] pre=new int[len];
		int[] next=new int[len];
		for (int i = 0; i < len; i++) {
			if (i==0) {
				pre[i]=-1;
			}
			
			//针对每个索引i的字符,看之前的i-1至0的字符是否出现此字符,
			//记录其索引
			int j;
			for ( j= i-1; j >=0; j--) {
				if (str.charAt(i)==str.charAt(j)) {
					pre[i]=j;
					break;
				}
			}
			
			if (j<0) {//没找到
				pre[i]=-1;
			}
		}
		
		//next[]初始化
		//花费时间主要在初始化,预估O(10^5*10^4)=O(10^9)
		//最差也是O(10^10)(不太可能,有break及不会循环10^5)
		for (int i = len-1; i >=0; i--) {
			if (i==len-1) {
				next[i]=len;
			}
			int j;
			for (j = i+1; j < len; j++) {
				if (str.charAt(i)==str.charAt(j)) {
					next[i]=j;
					break;
				}
			}
			if (j==len) {//没找到
				next[i]=len;
			}
		}
		
		//两个关键数组初始化完后,遍历输入字符
		int ans=0;//记录符合要求的子串的个数
		for (int i = 0; i < len; i++) {
			ans+=(i-pre[i])*(next[i]-i);//核心处
		}
		return ans;//最终的ans就是本题的最终结果(转换的思想)
	}
	
	//枚举每个子串,经测试只能过50%的测试用例
	static int sum1(String str) {
		int ans=0;
		for (int i = 0; i < str.length(); i++) {
			//每一轮清空
			String temp="";
			for (int j = i; j < str.length(); j++) {
				temp=temp+str.charAt(j);
				ans+=f(temp);
			}
		}
		return ans;
	}
	
	//求子串的值
	static int f(String s) {
		int ans=0;//统计字符恰好出现一次的个数
		int[] temp=new int[26];//遍历一遍s,存储26个字符的出现次数
		for (int i = 0; i < s.length(); i++) {
			char a=s.charAt(i);
			temp[a-'a']++;
		}
		for (int i = 0; i < temp.length; i++) {
			if (temp[i]==1) {
				ans++;
			}
		}
		return ans;
	}
}

测试用例如下:

ababc
21    //暴力枚举的方法,按行遍历
21    //优化,按列遍历,看题干的样例说明。

aaa
3
3

abcdfegsfsfkafs
405
405

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第15张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第16张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第17张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第18张图片
在这里插入图片描述
写了一点,感觉写不下去啦,好麻烦啊,分明就是考高中椭圆方程求解的问题。就是将数学求解的过程用代码实现,我感觉好麻烦啊。不想搞啦,浪费表情。
具体思路详见博客:人家用c++写的,涉及到坐标的变换
https://blog.csdn.net/m0_48960163/article/details/115124027

package com.lanqiao.十一届省赛第二场Java大学A组;

import java.util.Scanner;

//积分求和
//坐标转换,椭圆方程求解,三角形三条边方程的求解,针对x轴的积分,在定义域将其划分
//很多个小的矩形以逐渐覆盖椭圆和三角形相交的区域,小矩形分的越细,精度越高。
public class Class_09荒岛探测 {

	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		Point[] points=new Point[3];
		Point p1 = null,p2=null;//发射器和接收器的位置
		p1.x=scanner.nextDouble();
		p1.y=scanner.nextDouble();
		p2.x=scanner.nextDouble();
		p2.y=scanner.nextDouble();
		double L=scanner.nextDouble();
		
		//三角形三个顶点位置坐标
		for (int i = 0; i < points.length; i++) {
			points[i].x=scanner.nextDouble();
			points[i].y=scanner.nextDouble();
		}
		
		//扫描线的思路,用一条平行于y轴的直线设为xi,每次xi+0.001(本来估算+0.01的
		//不管测试时发现+0.001运行时间0毫秒),像积分那样将面积分成一个一个小矩形一点一点
		//的加起来,计算三角形和椭圆重叠部分面积等价于,所有相同xi下三角形分割出的矩形与椭圆
		//分割出的矩形重叠面积之和。
		//在有解情况下x=xi时,三角形与直线x=xi可以得到两个解y1,y2(y1<=y2),椭圆同理可得y3,y4(y3<=y4),
		//若min(y2,y4)>max(y1,y3),总面积sum加上(min(y2,y4)-max(y1,y3))*0.001.三角形的求解简单,椭圆的
		//比较麻烦。根据椭圆的定义写出其方程,对任意点(xi,y),有
		//((xi-xA)^2+(y-yA)^2)^1/2+((xi-xB)^2+(y-yB)^2)^1/2=L
		//解这个方程,实质是关于y的一元二次方程(把xi看作已知的常数)
		
	}
	
	static void rjd() {//求三角形的y1,y2
		
	}

	static void ty(){
	}//求椭圆的y3,y4
}

class Point{
	double x,y;
}

十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第19张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第20张图片
十一届蓝桥杯大赛软件类省赛第二场(Java大学A组)——2021.4.6_第21张图片
下面代码的整体思路:首先确定在没有前缀的情况下,最短长度的序列的最大逆序数(逆序对的数目)大于等于num,然后在这个长度下确定字典序最小的序列,这时就是确定在给定前缀和具体长度下的序列的最大逆序数是否大于等于num(输入幸运数V),这个过程就是构建符合要求的序列的过程。详情请参考博客:https://blog.csdn.net/weixin_46216553/article/details/115215457
代码如下:

package com.lanqiao.十一届省赛第二场Java大学A组;

import java.util.Scanner;

//参考博客:https://blog.csdn.net/weixin_46216553/article/details/115215457
public class Class_10字串排序 {

	/*
	 * 	题目:
	 * 	给定一个字符串,求解它的逆序数,字符串长度最大为1024,字母限定为{A,B,C,D}

  例如:

    输入:“DCBAB”

    输出:8

  解释:逆序串 [DC],[DB],[DA],[DB],[CB],[CA],[CB],[BA] 的总个数为逆序数
	 */
	/*
	 * 	思路:
	 * 	从后往前遍历字符串,用count_a,count_b,count_c来表示A,B,C出现的次数,同时定义count表示最终的逆序数

  具体规则:

    出现A:count_a + 1

    出现B:count_b + 1 & count + count_a   //B出现一次,加一次count_a,此时count记录的是BA的个数

    出现C:count_c + 1 & count + count_a + count_b  //C出现一次,加一次count_a(CA的个数)和count_b(CB的个数),此时count记录的就是BA的个数加上CA和CB的个数

    出现D:count + count_a + count_b + count_c  //D出现一次,在原来的count的基础上再加count_a(DA的个数) + count_b(DB的个数) + count_c(DC的个数)
	 */
	static int resolve(String str) {
		int count_a=0;
		int count_b=0;
		int count_c=0;//A,B,C出现的次数
		int count=0;//记录最终的逆序数
		for (int i = str.length()-1; i >=0; i--) {
			if (str.charAt(i)=='A') {
				count_a++;
			}else if (str.charAt(i)=='B') {
				count_b++;
				count+=count_a;//加BA的个数
			}else if (str.charAt(i)=='C') {
				count_c++;
				count+=count_a+count_b;//加CA和CB的个数
			}else if(str.charAt(i)=='D') {
				count+=count_a+count_b+count_c;
			}
		}
		return count;
	}
	
	static int[] list= {//存放后缀序列(a-z),插和删除容易
			0,0,0,0,0,//注:cccbba=1,2,3,0,......
			0,0,0,0,0,//注:ddddcccbba=1,2,3,4,0,......
			0,0,0,0,0,
			0,0,0,0,0,
			0,0,0,0,0,
			0
	};
	
	//'a'->0,'b'->1
	//300值如何确定的,str代表什么意思
	//前缀字符串长度不会大于300.
	//"abc":str={0,1,2}
	static int[] str=new int[300];//存放前缀序列
	
	static void reset() {//后缀序列清零
		int i=0;
		while(i<26&&list[i]!=0) {
			list[i]=0;
			i++;
		}
	}
	
	static int getrnum() {//计算逆序数,分三步
		int cnt=0;
		for (int i = 0; str[i]!=0; i++) {//前缀的逆序数
			for (int j = i; str[j]!=0; j++) {
				if (str[i]>str[j]) {//找到一对逆序加1 
					cnt++;
				}
			}
		}
		
		for (int i = 0; str[i]!=0; i++) {//前缀对后缀的逆序数(逆序对的数目)
			for (int j = 25; j>=0; j--) {
				if (str[i]-'a'>j) {//前缀+后缀=输出字符串(本题结果)
					//找到str[i]的字符比j表示的字符大,而j的个数为list[j]
					//所以有list[j]个str[i]和j表示的字符组合的逆序对。
					cnt+=list[j];
				}
			}
		}
		
		//这里所说的前缀是aexy这样的
		//后缀是cccbba,bba,ddddcccbba这样的
		//为什么是这种形式,涉及如何构建字符串的问题得到符合要求的逆序数V(就是冒泡交换的次数)。
		//特别注意前缀和后缀的特点
		
		int temp=0;//把list[i]理解为插入字符,记录索引0到i-1不同字符(这里的不同指的是与插入字符list[i]不同)的个数。
		for (int i = 0; i < 26; i++) {//后缀的逆序数
			cnt+=temp*list[i];
			temp+=list[i];//加上不同字符的个数为temp与下一轮循环i+1表示的字符组成一对,而i+1表示的字符个数为list[i+1],所以会有temp*list[i+1]个逆序对
		}
		return cnt;
	}
	
	//获得最大逆序增量(特殊步骤中代替求逆序函数用来提速)
	//可以认为在数字符串里有多少非c(传入的参数)字符
	//也就是插入c逆序数能增加多少,就是看增加了多少逆序对,注意这里的理解
	static int getinc(int c) {//这里c取值范围0-25,对应字符a-z
		int i=0,cnt=0;
		//先看前缀序列
		while(str[i]!=0) {//非a字符
			if (str[i]>(c+'a')) {//这里假设c不插入前缀,插入后缀
				cnt++;
			}
			i++;
		}
		for (i = 0; i < 26; i++) {//c插入后缀
			if (i!=c) {
				cnt+=list[i];//  dddcbba;c前面多少个字符,c后面多少个字符,二者的和就是这些字符与c组成的逆序对的数目
			}
		}
		return cnt;
	}
	
	//在后部序列中插入元素,保证逆序数最大
	static void set() {
		int max=0,temp=0,index=0;
		for (int i = 0; i < 26; i++) {
			list[i]++;//每个字符都加一遍,看哪个的逆序数最大
			if ((temp=getinc(i))>max) {
				//找出使逆序数增的最快的字符插入(这里比用增而直接记录逆序数不影响结果
				//但慢一些,数据10000左右要5秒左右,会超时,不然我不会编这么个多余的函数)
				index=i;
				max=temp;
			}
			list[i]--;//还原
		}
		list[index]++;//加index表示的字符逆序数最大
	}
	
	//获取前缀确定且长度确定的前提下的最大逆序字符串
	static void getMaxStr(int l) {//l为长度确定的值
		reset();
		for (int i = 0; str[i]!=0; i++,l--);//l减去前缀序列的长度,剩下的就是后缀序列
		while(l>0) {
			set();//这是一个构造后缀序列的过程,在前缀确定的情况下
			l--;
		}
	}
	
	static void printStr() {//打印目标字符串
		String s="";
		int i=0;
		while(str[i]!=0) {
			s+=(char)str[i];//加前缀序列
			i++;
		}
		for (i = 25; i >= 0; i--) {//这里其实没用,既然不执行也不会影响效率,留着把,后缀最后是空的,但曾经存在过的
			//list中表示的就是类似这样的zzzzyyy....bbbaa.(这里的每个字符出现的次数>=0)
			for (int j = 0; j < list[i]; j++) {
				s+=(char)(i+'a');//i=25,有几个z就加上几个z
			}
		}
		System.out.println(s);
	}
	
	static void getans(int num,int l) {//l是字串的长度
		for (int i = 0; i < l; i++) {
			for (int j = 0; j < 26; j++) {//每个位从a开始试
				str[i]=j+'a';
				getMaxStr(l);//获取指定前缀且长度确定的最大逆字串
				if (getrnum()>=num) {//超了就下一个
					break;
				}
			}
		}
	}
	
	public static void main(String[] args) {
		int num;
		Scanner scanner=new Scanner(System.in);
		num=scanner.nextInt();
		scanner.close();
		int l=0;
		while(getrnum()<num) {//获取最短字串长
			++l;
			getMaxStr(l);
		}
		getans(num, l);//获取目标字串
		printStr();
	}
}

感受:Java大学A组的题目真不是盖的,最后四道大题才是大餐。完整的正确的做出来,而且在规定的时间,难。

你可能感兴趣的:(算法)