蓝桥杯自描述序列

题目描述

标题:自描述序列

小明在研究一个序列,叫Golomb自描述序列,不妨将其记作{G(n)}。这个序列有2个很有趣的性质:

  1. 对于任意正整数n,n在整个序列中恰好出现G(n)次。
  2. 这个序列是不下降的。

以下是{G(n)}的前几项:

n 1 2 3 4 5 6 7 8 9 10 11 12 13
G(n) 1 2 2 3 3 4 4 4 5 5 5 6 6

给定一个整数n,你能帮小明算出G(n)的值吗?

输入

一个整数n。

对于30%的数据,1 <= n <= 1000000
对于70%的数据,1 <= n <= 1000000000
对于100%的数据,1 <= n <= 2000000000000000

输出

一个整数G(n)

【样例输入】
13

【样例输出】
6

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
不要使用package语句。不要使用jdk1.7及以上版本的特性。
主类的名字必须是:Main,否则按无效代码处理。

java代码1

大体思路是模拟序列填表的过程,对于数组data,对于每一个下标i我们填入一个数字num,判断此次填入的数字是否和之前的表发生了冲突,如果发生冲突,显然当前数字我们不能填在当前下标,需要将num++来填当前的data[i]。
如果没有发生冲突,下标i++接着填下一个位置的表即可。

import java.util.Scanner;

public class Main {
	static int n;
	static int[] data;
	static final int max = 10000000;
	static int num = 1;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		data = new int[max];
		data[0] = 0;
		// i从1遍历到n,对于每一个维护数组data
		for (int i = 1; i <= n; i++) {
			data[i] = num;
			// 当前在下标i的位置填入num,不仅表示data[i]奖来应该出现num次,也表示num的出现次数加一
			// 那么我们应该检查num到底出现了多少次,是否和data[num]的值一致
			// 若num出现的次数大于data[num],说明在与之前填好的表发生了冲突,不能在此处填入num
			if (conflict(num, i)) { // data[i]的值冲突,说明不能填入num,由于该序列递增,num++
				num++;
				i--; // 仍然处理该位置,不往后走
			}
		}
		// for (int i = 0; i <= n+1; i++) {
		// System.out.print(data[i]+" ");
		// }
		System.out.println(data[n]);
		sc.close();
	}

	/**
	 * 
	 * @param num
	 *            给出data下标,观察该段内容是否出现冲突
	 * @return
	 */
	private static boolean conflict(int begin, int end) {
		int cnt = 0;
		for (int i = begin; i <= end; i++) {
			if (begin == data[i]) {
				cnt++;
			}
			if (cnt > data[begin]) {
				return true;
			}
		}
		return false;
	}
}

java代码2

上述填表过程中,我们注意到有很多空间被浪费掉了,比如填入num之后的conflict检查之前的数组并没什么用处,我们完全可以把这些内存节省下来,来实现更好的内存利用。
怎么实现呢?我不会。

java代码3

有一个思路是:我们可以通过之前已经得到的G(n)来往后推测一些G(k),k>n。
比如我们想知道G(3)的值,假设我们已经知道G(1)=1,G(2)=2,显然在G序列中会有一个1,两个2。
也就是说我们就能得到G(2),G(3)的值都是2。
比如我们想知道G(4)的值,假设我们已经知道G(1)=1,G(2)=2,G(3)=2,显然在G序列中会有一个1,两个2,两个3,那我们就能得到G(2),G(3)的值是2,G(4),G(5)的值是3。在这个例子中1+2=3,1+2+2=5,那么我们就可以通过G(1),G(2),G(3)来得到G(3+1)=G(4)到G(5)的值。
不过由于时间限制,只能构造一部分数组来推算后面的内容。
代码如下:

import java.util.Scanner;

public class Main {
	static int n;
	static int[] data;
	static int num = 1;
	static long sum = 0;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		long time1 = System.currentTimeMillis();
		data = new int[80010];
		data[0] = 0;
		// i从1遍历到n,对于每一个维护数组data
		for (int i = 1; i <= 80000; i++) {
			data[i] = num;
			// 当前在下标i的位置填入num,不仅表示data[i]奖来应该出现num次,也表示num的出现次数加一
			// 那么我们应该检查num到底出现了多少次,是否和data[num]的值一致
			// 若num出现的次数大于data[num],说明在与之前填好的表发生了冲突,不能在此处填入num
			if (conflict(num, i)) { // data[i]的值冲突,说明不能填入num,由于该序列递增,num++
				num++;
				i--; // 仍然处理该位置,不往后走
			}
		}
		// for (int i = 0; i <= n+1; i++) {
		// System.out.print(data[i]+" ");
		// }
		if (n == 1) {
			System.out.println(1);
			return;
		}
		for (int i = 1; i <= 80000; i++) {
			num = data[i];
			sum += num;
			if (sum >= n) {
				System.out.println(i);
				System.out.println(System.currentTimeMillis() - time1+"ms"); // 输出运算时间
				return;
			}
		}
		sc.close();
	}

	/**
	 * 
	 * @param num
	 *            给出data下标,观察该段内容是否出现冲突
	 * @return
	 */
	private static boolean conflict(int begin, int end) {
		int cnt = 0;
		for (int i = begin; i <= end; i++) {
			if (begin == data[i]) {
				cnt++;
			}
			if (cnt > data[begin]) {
				return true;
			}
		}
		return false;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值