看似上面的计算过程没什么规律,不过还是可以找到规律的。就是如果当前位为 a ,截止当前 a 出现了 b 次,比 a 小的数有 c 个,那么当前位给 s 的贡献即为 \frac{t\times c}{b} 。
为什么这个规律是正确的?考虑一个比 a 小的数 x ,如果之前 x 出现了 y 次,那么将 x 放到这一位,再把 a 放到后面,根据可重排列公式, t 的值就要变成 \frac{t\times y}{b} 。所有比 a 小的数共出现了 c 次,将 y 替换成 c 就是 \frac{t\times c}{b} 。
此时我们只需模拟上述过程,就能得到一个 O(mp) 的做法。
如果 n 是一个 10^6 进制数,也就是 p=10^6 ,这个复杂度我们也无法接受。我们需要支持两种操作:向一个序列中插入一个数,求序列中小于一个数的数的个数。显然可以用树状数组优化一下,就成了 O(m\log p) 。
代码如下:
#include<cstdio>
#include<cstring>
char str[55];
int p[11],q[11];//p为每个数出现次数,q为树状数组
int add(int x){
for(;x<11;x+=x&-x)++q[x];
}
int sum(int x){
int r=0;
for(;x;x-=x&-x)r+=q[x];
return r;
}
int main(){
int n,i,c,a;
__float128 s=0,t=1;//用整型中间变量会溢出!
scanf("%s",str),n=strlen(str);
for(i=n-1;i>=0;--i){
a=str[i]-'0'+1;
c=sum(a-1);//a为当前数,c为比a小的数个数
if(!c)t=t*(n-i)/(++p[a]);
else{
t/=(++p[a]);
s+=t*c;
t*=(n-i);
}
add(a);
}
printf("%lld",(long long)(s+0.5));
return 0;
}