P2518 [HAOI2010] 计数 题解
原题链接:计数
初看题:“嗯?什么意思?”
翻译一下,每次给出一个整数
我们思考这样一件事情:把0删掉其实就等价于把0移动到最前面
所以接下来问题就简化成了“把所有数重新排序,求得到的数的大小小于
我们考虑到对于两个数
从这一点出发,我们尝试确定每一位的数。
假设有这么一组数据
我们现在开始确定第一位的数,因为
此时第一位还能为 5,那我们就确定第一位的数为 5,尝试确定第二位的数,第二位可以是
Wait a minute,数据之中有两个 1 ,此时构成的全排列虽然编号不同,但是得到的数可能是相同的,也就是说,直接用阶乘计算可能会导致计算重复。
问题就转变成为了:如何求可重集的全排列。
设总元素个数为
如果第
同理,得总共的方案数为
事实上就只是每种数的数量与剩余空位数量的组合数的乘积而已。
考虑到定义域很小,我们可以先用一个二维数组预处理出各个组合数的答案。
可以用以下式子快速预处理出组合数
证明就不给出啦,感兴趣的可以尝试着自己证明看看
Talk is cheap,show me the code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Max=100;
int tot[Max],num[Max],n=0;
int C[Max][Max];// r n
int ans=0;
int solve(int len)
{
int fin=1;
for(int i=0;i<=9;i++)
{
fin*=C[tot[i]][len];
len-=tot[i];
}
return fin;
}
signed main()
{
C[0][0]=1;
for(int N=1;N<=60;N++)
{
C[0][N]=1;
C[N][N]=1;
for(int R=1;R<N;R++)
{
C[R][N]=C[R-1][N]*(N-R+1)/R;
}
}
char c;
c=getchar();
while('0'<=c and c<='9')
{
num[++n]=c-'0';
tot[c-'0']++;
c=getchar();
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<num[i];j++)
{
if(tot[j])
{
tot[j]--;
ans+=solve(n-i);
tot[j]++;
}
}
tot[num[i]]--;
}
printf("%lld",ans);
}