洛谷阅读更佳: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=a∗k,其中 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 2n−1 ,由此得出 ( a , b ) (a, b) (a,b) 之间含有 2 n − 1 2^n - 1 2n−1 个数。最后再加上 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 p≤1024 所以这个间隙最大也不会很多数字。
代码
枚举间隙数字的部分如果直接枚举会爆栈,使用迭代加深搜索就可以过了。
#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;
}