【题解】[AHOI2002] 网络传输 By 5ab as a juruo.

题目

题目来源:CCF 安徽省选 2002;

评测地址:Luogu#2558

题目描述

在计算机网络中所有数据都是以二进制形式来传输的。但是在进行较大数据的传输时,直接使用该数的二进制形式加以传输则往往传输的位数过多。譬如要传输 (1024) 就需要 (11) 位二进制数。于是小可可提出了一种数据优化传输的设想,并打算对这一设想进行试验。

该设想是:正整数的所有方幂以及任意多个互不相等的 (k) 的方幂之和排成一个递增数列 (left<a_k ight>),例如当 (k=3) 时,(left<a_k ight>) 的前 (7) 项为 (1)(即 (3^0))、 (3)(即 (3^1))、(4)(即 (3^0+3^1))、(9) (即 (3^2))、(10)(即 (3^0+3^2))、(12)(即 (3^1+3^2))、(13)(即 (3^0+3^1+3^2))。

如果数 (d) 是数列 (left<a_k ight>) 中的第 (p) 项,则可以通过传送 (k)(p) 这两个数来表示数 (d)。由于 (k)(p) 这两个相对很小的数就可以表达出很大的数 ,因而理论上可以减少网络传输的位数。

小可可现在请你编写程序把接收到的数 (k)(p) 所代表的数d计算出来。

输入格式

文件中以一行的形式存放了两个正整数 (k)(p)

输出格式

以一行的形式输出问题的解(解的位数不超过 (50) 位)。

数据规模及约定

评测时间限制 (1000 extrm{ms}),空间限制 (128 extrm{MiB})

(1<kle 1024)(1le ple 1024)

分析

题意是说给你一个由 (k^n) 的和构成的 递增 数列 (left<t ight>),求出这个数列的第 (p) 项。

观察这些和之间的大小关系,不难发现一个性质:(large{k^n>sumlimits_{i=1}^{n-1} k^i})

[egin{aligned} sumlimits_{i=1}^{n-1} k^i & =dfrac{k^n-1}{k-1}\ & le k^n-1<k^n end{aligned} ]

也就是说,所有加上了 (k^n) 的数必然比所有没有加上 (k^n) 的数(包括更大的数)要大。也就是说 (k^n) 的选与不选满足单调性。

我们定义 (p=sumlimits_{i} a_i imes 2^i),即 (left<a ight>)(p) 的二进制表达。

只需证明 (t_p=sumlimits_{i} a_i imes k^i) 即可。

我们尝试用数学归纳法证明:初始,显然 (t_1=k)

考虑 (t_v left(vin left[2^n,2^{n+1} ight) ight)),利用单调性,易得 (t_v) 必然选择了 (k^n)

注意到这一段区间共有 (2^n) 个,而之前也有 (2^n) 个。根据单调性和数列定义,易得 (t_v=t_{v-2^n+1}+k^n),满足二进制。

证明了这个性质,接下来只需要对 (p) 进行二进制分解,然后预处理出 (k^n),最后遍历一遍就可以得到答案。

Code

别忘了加上高精度哦!

#include <cstdio>
#include <cstring>
using namespace std;

const int max_lgm = 12; // 最大的 n

struct long_int // 高精度(主要码量来源 hh)
{
	typedef char B;
	static const int max_n = 50;

	B v[max_n+1]; // 奇怪的写法增加了(误
	int len;

	long_int() : len(0)
	{
		memset(v, 0, sizeof(v));
	}

	void assign(int n)
	{
		memset(v, 0, sizeof(v));
            len = 0;

		while (n > 0)
		{
			v[len] = n % 10;
			n /= 10, len++;
		}
	}

	void operator+=(const long_int& a)
	{
		for (int i = 0; i <= len || i <= a.len; i++)
		{
			v[i+1] += (v[i] + a.v[i]) / 10;
			v[i] = (v[i] + a.v[i]) % 10;
		}

		if (a.len > len)
			len = a.len;
		
		while (!v[len])
			len--;
		
		len++;
	}

	void operator*=(const long_int& a)
	{
		B tmp[max_n+1] = {};

		for (int i = 0; i < a.len; i++)
		{
			for (int j = 0; j < len; j++)
			{
				tmp[i+j+1] += (tmp[i+j] + a.v[i] * v[j]) / 10;
				tmp[i+j] = (tmp[i+j] + a.v[i] * v[j]) % 10;
			}
		}

		len += a.len;
		while (!tmp[len])
			len--;
		len++;

		for (int i = 0; i < len; i++)
			v[i] = tmp[i];
	}

	void out()
	{
		for (int i = len - 1; i >= 0; i--)
			putchar('0' + v[i]);
	}

	void outln()
	{
		out();
		putchar('
');
	}

    void operator=(const long_int& a)
    {
        len = a.len;

        for (int i = 0; i < len; i++)
            v[i] = a.v[i];
    }
};

long_int pw[max_lgm];

int main()
{
	int p, k;
	long_int tmp;

	scanf("%d%d", &p, &k);
	
	pw[0].assign(1); // 预处理
	tmp.assign(p);
	for (int i = 1; i < max_lgm; i++)
    {
        pw[i] = pw[i-1];
		pw[i] *= tmp;
    }
	
    /*
    tmp.outln();
    for (int i = 0; i < max_lgm; i++)
        pw[i].outln();
    */
    
	tmp.assign(0);
	for (int i = 1, j = 0; i <= k; i <<= 1, j++) // 二进制分解+统计答案
		if (i & k)
			tmp += pw[j];
	
	tmp.outln(); // 精准结束

	return 0; // 等待的就是 AC 喽
}
原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-ahoi2002_lg2558.html