CF1267K 密钥存贮
1 CF1267K 密钥存贮
2 题目描述
时间限制 \(3s\) | 空间限制 \(512M\)
\(Karl\) 正在开发一种密钥存贮服务,每一个用户有一个正整数的密钥。\(Karl\) 知道非加密纯文本存储有安全问题,所以他不存储密钥本身,只是存储密钥的一个指纹。但是使用已有的指纹算法看起来没什么意思,他准备自己发明一个算法。\(Karl\) 的指纹是这么计算的:对给定的整数先除以 \(2\), 然后再除以 \(3\),再除以 \(4\),一直除下去直到结果为 \(0\)。指纹定义为这些除法运算的所有余数集合。例如,对于密钥 \(11\) 是这么计算的:\(11\) 除以 \(2\) 等于 \(5\),余数为 \(1\),然后 \(5\) 除以 \(3\) 结果为 \(1\),余数为 \(2\),\(1\) 除以 \(4\) 结果为 \(0\),余数为 \(1\)。这样密钥 \(11\) 的余数序列集合为 \([1,2,1]\),指纹的集合为 \([1,2,1]\)。
\(Ksenia\) 想证明 \(Karl\) 的指纹算法不是很好,例如,她发现密钥 \(128800\) 和
\(123456\) 都产生了相同的指纹 \({0,0,0,0,2,3,3,4}\)。这样,用户的密钥很可能和最容易猜的密钥的指纹重合,比如 \(123456\)。\(Ksenia\) 想让她的劝说更有说服力。她想计算出给定的常用密钥列表中的每个密钥有多少个同样指纹的其他密钥。
数据范围:\(1 ≤ 𝑡 ≤ 50000\), \(1 ≤ 𝑘_𝑖 ≤ 10^{18}\)
3 题解
注:我们将排序过后的余数序列称为无序的余数序列(意为不在乎顺序的余数序列),将排序前的余数序列称为有序的余数序列(意为在乎顺序的余数序列)。
首先我们观察到一个性质:对于某一个有序的余数序列,其对应的数一定是唯一的。也就是说,我们只需要考虑将一个无序的余数序列进行任意顺序的打乱最终能得到的方案数。这时要注意,若最后一个余数为 \(0\),那么该有序的余数序列则不满足要求,这类情况要被减去。具体地,我们来看如何计算方案数:
因为一个数模 \(p\) 的余数肯定比 \(p\) 小,所以更大的数有更少的选择。而如果我们先算小一些的余数,我们就无法确定这些余数的位置是否在大的余数可以取到的位置。如此一来,计算就十分麻烦。为了避免出现以上情况,我们先考虑大一些的余数,再考虑小的余数,这样我们后选择的数的范围一定比先选择的数的范围大。此时我们假设之前已经被占用了 \(k\) 个数,而且当前整个余数序列长度为 \(len\),当前余数为 \(i\),当前余数的个数为 \(Mod_i\),那么方案数为 \(C_{len - i - k}^{Mod_i}\)。随后 \(k\) 需要加上 \(Mod_i\),\(i\) 则继续 \(-1\)。
这里我们为了需要减去的那些方案数是将 \(Mod_0\) 个数 \(-1\) 后将总 \(len-1\),再重新计算一次的总方案数:所有最后一个余数为 \(0\) 的情况。
组合数利用 \(C_{n}^{m} = C_{n-1}^{m} + C_{n-1}^{m-1}\) 预处理即可。注意答案要输出 \(ans-1\),因为我们要找的是与当前数列余数序列相同的数的个数。
4 代码(空格警告):
#include <iostream>
#include <cstring>
using namespace std;
const int N = 25;
#define int long long
int T, x, cnt, ans;
int Mod[N], c[N][N];
int sol(int len)
{
int rt = 1, l = 0;
for (int i = len; i >= 1; i--)
{
if (len - i - l < Mod[i]) return 0;
if (Mod[i])
{
rt *= c[len-i-l][Mod[i]];
l += Mod[i];
}
}
return rt;
}
signed main()
{
c[0][0] = 1;
for (int i = 1; i <= 25; i++) c[i][0] = c[i][i] = 1;
for (int i = 1; i <= 25; i++) for (int j = 1; j < i; j++) c[i][j] = c[i-1][j] + c[i-1][j-1];
cin >> T;
while (T--)
{
cin >> x;
cnt = 2;
memset(Mod, 0, sizeof(Mod));
while (x)
{
Mod[x % cnt]++;
x /= cnt;
cnt++;
}
ans = sol(cnt-1);
if (Mod[0])
{
Mod[0]--;
ans -= sol(cnt-2);
}
cout << ans-1 << '\n';
}
return 0;
}