题解:P2558 [AHOI2002] 网络传输

洛谷阅读更佳:https://www.luogu.com.cn/article/ach90ehg

题意

设一个数列 d = { d 1 , d 2 , d 3 . . . . d n } d = \{ d_1,d_2,d_3....d_n \} d={d1,d2,d3....dn} 对于任意的 d i d_i di 一定由 k k k 的不重复若干次幂 n n n 相加获得,其中 n n n 是自然数。现在给定 k , p k,p k,p,求数列中的 d p d_p dp 的值。

思路

做完后翻了题解区,发现自己挺笨的,但是能做出来也就满足了。

给大家提供一种新的思考方式吧。

通过观察发现,设 x x x 是第 p p p 位数字并且一个 k k k 的完全平方数。 x = k a x = k^{a} x=ka 其中 a a a 是自然数。

结论:它的下一个完全平方数的位置 $p’ = p + 2^a $ 。

证明:根据题目给出的要求,对于任意排名 p p p 的数都由 不重复 的若干 k k k 的自然数次幂相加获得,所以对于任意排名为 p p p 的值可以分情况讨论:

  • 1、它是一个完全平方数,仅由自己组成。
  • 2、它不是完全平方数,所以由前面的所有完全平方数相加组成。

两个完全平方数之间夹着的一定不是完全平方数,而这样的平方数有几个?设左端点为 a = k n a = k^{n} a=kn ,右端点为 b = a ∗ k b = a * k b=ak,其中 n n n 为自然数。要组合出 ( a , b ) (a, b) (a,b) 之间数的数量,根据我们刚才的推论,它一定由 [ 1 , a ] [1, a] [1,a] 之间的完全平方数组合得。

问题就变成了从 [ 0 , n ] [0, n] [0,n] 这些次幂中挑若干个使得它们加起来在 ( a , b ) (a, b) (a,b) 之间。如果要大于 a a a ,那 a a a 一定要选了,因为前面的数都小于它,剩下的话在前面的数选多少个都可以了,根据组合公式 C n 0 + C n 1 + C n 2 + . . . . . C n n = 2 n C^0_n + C^1_n + C^2_n + ..... C^n_n = 2^n Cn0+Cn1+Cn2+.....Cnn=2n ,剔除掉都不选的方案就是 2 n − 1 2^n - 1 2n1 ,由此得出 ( a , b ) (a, b) (a,b) 之间含有 2 n − 1 2^n - 1 2n1 个数。最后再加上 1 1 1 ,就得到 b b b 的位次了。

证毕。

因为我们只对题目询问的第 p p p 个位置的数字感兴趣,所以它前面的数字都可以跳过,因此我们可以通过刚才得出的结论,根据排名枚举到距离排名 p p p 最近的完全平方数 b a s e base base

枚举完后,此时如果 b a s e base base 就是完全平方数,输出即可,否则尝试枚举下个完全平方数之间的所有可能的数,再找到符合排名的即可。因为 p ≤ 1024 p \le 1024 p1024 所以这个间隙最大也不会很多数字。

代码

枚举间隙数字的部分如果直接枚举会爆栈,使用迭代加深搜索就可以过了。

#include <iostream>
#include <vector>
#include <set>
#include <string>
using namespace std;
class BigInt : public vector<int> {
public:
	BigInt() { push_back(0); }
	BigInt(string str) {
		for (int x = str.size() - 1; x >= 0; x--) push_back(str[x] - '0');
		clear_invalid_zero();
	}
	bool operator< (const BigInt& other) const {
		int n = size(), m = other.size();
		if (n != m) return n < m;
		for (int x = n - 1; x >= 0; x--) {
			if (at(x) > other[x]) return false;
			if (at(x) < other[x]) return true;
		}
		return false;
	}
	BigInt operator+ (const int& other) const {
		BigInt ret(*this);
		ret[0] += other;
		int carry = ret[0] / 10, ind = 1;
		while (carry) {
			if (ind >= ret.size()) ret.push_back(0);
			ret[ind] += carry;
			carry = ret[ind] / 10;
			ret[ind] %= 10;
		}
		ret.clear_invalid_zero();
		return ret;
	}
	BigInt operator* (const int& other) const {
		BigInt ret(*this);
		int n = ret.size(), ind = 0, carry = 0;
		while (carry || ind < n) {
			if (ind >= ret.size()) ret.push_back(0);
			if (ind < n) ret[ind] *= other;
			ret[ind] += carry;
			carry = ret[ind] / 10;
			ret[ind] %= 10;
			ind++;
		}
		ret.clear_invalid_zero();
		return ret;
	}
	BigInt operator+ (const BigInt& other) const {
		BigInt ret(*this);
		int m = other.size(), ind = 0, carry = 0;
		while (ind < m || carry) {
			if (ind >= ret.size()) ret.push_back(0);
			if (ind < m) ret[ind] += other[ind];
			ret[ind] += carry;
			carry = ret[ind] / 10;
			ret[ind] %= 10;
			ind++;
		}
		ret.clear_invalid_zero();
		return ret;
	}
	void clear_invalid_zero() {
		for (int x = size() - 1; x; x--) {
			if (at(x)) break;
			pop_back();
		}
	}
	void print() const {
		for (int x = size() - 1; x >= 0; x--)
			printf("%d", at(x));
		printf("\n");
	}
};

set<BigInt> ans;
vector<BigInt> _select;
void dfs(int ind, int n, int max_dep, int dep, BigInt d) {
	//迭代加深
	if (dep == max_dep) {
		//理论上这个枚举是不会重复的,但是set可以排序,我就用这个了,用vector也是可以的
		ans.insert(d);
		return;
	}
	if (max_dep - dep > n - ind || ind == n) return;
	//选
	dfs(ind + 1, n, max_dep, dep + 1, d + _select[ind]);
	//不选
	dfs(ind + 1, n, max_dep, dep, d);
}
int quick_mi(int b) {
	int res = 1, a = 2;
	while (b) {
		if (b % 2) res *= a;
		a *= a;
		b /= 2;
	}
	return res;
}
int main() {
	int k, p;
	cin >> k >> p;
	if (p <= 2) {
		cout << (p == 1 ? 1 : k);
		return 0;
	}
	int s = 2;
	int last = 1;
	BigInt base(to_string(k));
	//枚举到最近的完全平方数
	while (s + quick_mi(last) <= p) {
		s += quick_mi(last);
		base = base * k;
		last++;
	}
	if (s == p) { //如果他就是一个完全平方数
		base.print();
		return 0;
	}
	//否则应由base和 k^{0~last}次方组成
	BigInt _enum("1");
	//枚举出所有的可选数字
	for (int x = 0; x < last; x++) {
		_select.push_back(_enum);
		_enum = _enum * k;
	}
	//使用迭代加深搜索枚举剩下的组合
	for (int x = 1; x <= last; x++)
		dfs(0, _select.size(), x, 0, base);
	for (auto it : ans) {
		s++;
		if (s == p) {
			it.print();
			return 0;
		}
	}
	return 0;
}

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值