题目描述
标题:自描述序列
小明在研究一个序列,叫Golomb自描述序列,不妨将其记作{G(n)}。这个序列有2个很有趣的性质:
- 对于任意正整数n,n在整个序列中恰好出现G(n)次。
- 这个序列是不下降的。
以下是{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;
}
}