CF1267K Key Storage 题解
前言。
所谓的简单组合计数题卡了我一下午。我是真被样例中的这个
翻译题意:
卡尔正在开发一种密钥存储服务。每个用户都有一个正整数密钥。卡尔知道,用纯文本存储密钥是不好的做法。因此,他决定不存储密钥,而是存储密钥的指纹。
卡尔的指纹是这么计算的:对给定的整数先除以
举个例子,例如
- 首先
11 除以2 的结果为5 余数为1 。 - 其次
5 除以3 结果为1 余数为2 。 - 最后
1 除以4 结果为0 余数为1 。
密钥的余数集合就是
有人认为用户的密钥很可能和最容易猜的密钥的指纹重合,即产生相同指纹。她想计算出给定的常用密钥列表中的每个密钥有多少个同样指纹的其他密钥。
分析。
前置知识:一是组合数预处理二是组合数的原理然后阅读以下题解。
我们假定排序过后的余数序列
我们发现,对于任意一个序列
但是如果最后一个余数为
假定一个情况,然后反证。
因为一个数模
所以为了便于计算,我们就反其道而行之,考虑大余数,再考虑小余数。这样因为余数越大时,其可能包含的余数越大,则后选择的数的范围一定比最先选择的数的范围要大。
那么此时假设之前已经被抢占了
显然我们每次询问都要计算一次,考虑到数据范围,所以要预处理组合数。组合数可以用
代码如下,仅供参考:
#include<iostream>
#include<cstring>
#define ll long long
using namespace std;
ll t,k;
ll cnt,ans;
ll tot[30],sum[30][30];
ll work(ll chdu){
ll res=1,sed=0;
for (int i=chdu;i>=1;i--){
ll q=chdu-i-sed;
if(q<tot[i]){
return 0;
}
if(tot[i]){
res*=sum[q][tot[i]];
sed+=tot[i];
}//公式计算。
}
return res;
}
void init(){
sum[0][0]=1;
for (int i=1;i<=25;i++){
sum[i][0]=sum[i][i]=1;
}
for(int i=1;i<=25;i++){
for(int j=1;j<i;j++){
sum[i][j]=sum[i-1][j]+sum[i-1][j-1];
}
}
}//预处理。
int main(){
init();
cin>>t;
while(t--){
cnt=2;
memset(tot,0,sizeof(tot));
cin>>k;//多测不清空,样例过不去。
while(k){
tot[k%cnt]++;
k/=cnt;
cnt++;
}
ans=work(cnt-1);
if(tot[0]){
tot[0]--;
ans-=work(cnt-2);
}
ans--;//特判掉自己。
cout<<ans<<"\n";
}
return 0;
}
后记。
注意小细节,有缜密的思路,然后看看数据范围,这道题还是不难解决的。
大家如有疑问,可以在评论区提出,我会尽力解答的。