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

题目

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

评测地址:Luogu#2558

题目描述

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

该设想是:正整数的所有方幂以及任意多个互不相等的 \(k\) 的方幂之和排成一个递增数列 \(\left<a_k\right>\),例如当 \(k=3\) 时,\(\left<a_k\right>\) 的前 \(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\right>\) 中的第 \(p\) 项,则可以通过传送 \(k\)\(p\) 这两个数来表示数 \(d\)。由于 \(k\)\(p\) 这两个相对很小的数就可以表达出很大的数 ,因而理论上可以减少网络传输的位数。

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

输入格式

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

输出格式

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

数据规模及约定

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

\(1<k\le 1024\)\(1\le p\le 1024\)

分析

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

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

\[\begin{aligned} \sum\limits_{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=\sum\limits_{i} a_i\times 2^i\),即 \(\left<a\right>\)\(p\) 的二进制表达。

只需证明 \(t_p=\sum\limits_{i} a_i\times k^i\) 即可。

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

考虑 \(t_v\ \left(v\in \left[2^n,2^{n+1}\right)\right)\),利用单调性,易得 \(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('\n');
	}

    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 喽
}
posted @ 2020-08-10 22:51  5ab  阅读(72)  评论(0)    收藏  举报