题意:给出串S1,S2,长度相同,均为小写字母,保证S1<=S2(字典序),现在要求将S1重新排列,得到T,使得S1<T<S2,求这样的T的个数。如果T1=T2,那么认为是一种。
题解:先考虑如何让T<S2,这样求出结果之后,再求一次T<S1,减一下,得到S1<=T<S2,再减1就得到S1<T<S2。
我们可以从第一位向后枚举,在每一位上枚举这一位可以放哪个字母,比如S1是ababa,S2是ububu,那么我们先枚举T第一位放a,那么无论后边怎么放,一定满足T<S2,于是就是算一个有重复元素的全排列,公式是(元素总个数!)/(元素1个数!*元素2个数!*……元素x个数!)。然后这一位可以放的字母范围是‘a’-S2[ i ]-1,枚举结束之后,把这一位放上S2[ i ],然后继续看后边一位,意思是我前缀一段保持和S2相同,让后边的位来分胜负。如果S2[ i ]这种字母用光了,显然我们也就统计完了,因为必须在这一位分出胜负。
于是我们设计了一个n*26*26的算法。然后这算法被卡掉了。。。。。
优化:考虑那个公式(元素总个数!)/(元素1个数!*……元素x个数!)。我们每次往下推进一位,带来的变化是元素总个数-1,某个元素个数-1,于是给这个公式带来的变化就是乘一个数字,除一个数字而已。。。所以可以每次不用重新算这个公式。每次让当前位放上一个字母,算答案的时候,也是只该元素个数-1->乘上一个数字。
优化后:n*26+打表算阶乘和逆元。
代码有点丑,,但还是好理解的,,,因为,,,辣鸡葫芦娃又送了。。。
Code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+100;
const int MOD = 1e9+7;
int cnt[1000];
char s1[maxn],s2[maxn];
int n;
long long ans =0;
long long bas[maxn];
long long ni[maxn];
inline long long q(long long x,long long y){
long long res =1;
while (y){
if (y&1){
res = res*x%MOD;
}
x = x*x%MOD;
y>>=1;
}
return res;
}
inline long long calc(int tot){
long long res = bas[tot];
for (int i='a';i<='z';i++){
if (cnt[i])
res = res*q(bas[cnt[i]],MOD-2)%MOD;
}
return res;
}
int main(){
bas[0]=1;
ni[0]=1;
for (int i=1;i<=1000000;i++){
bas[i] = bas[i-1]*i%MOD;
ni[i] = q(i,MOD-2);
}
gets(s1);
gets(s2);
n =strlen(s1);
for (int i=0;i<n;i++){
cnt[s1[i]]++;
}
long long temp =calc(n);
for (int i=0;i<n;i++){
temp = temp*ni[n-i]%MOD;
for (int x='a';x<s2[i];x++){
if (cnt[x]){
ans+=temp*cnt[x]%MOD;
}
}
ans%=MOD;
temp = temp*cnt[s2[i]]%MOD;
cnt[s2[i]]--;
if (cnt[s2[i]]<0)break;
}
long long ans1 = ans;
ans =0;
memcpy(s2,s1,strlen(s1));
memset(cnt,0,sizeof cnt);
for (int i=0;i<n;i++){
cnt[s1[i]]++;
}
temp =calc(n);
for (int i=0;i<n;i++){
temp = temp*ni[n-i]%MOD;
for (int x='a';x<s2[i];x++){
if (cnt[x]){
ans+=temp*cnt[x]%MOD;
}
}
ans%=MOD;
temp = temp*cnt[s2[i]]%MOD;
cnt[s2[i]]--;
if (cnt[s2[i]]<0)break;
}
cout<<(ans1-ans+2*MOD-1)%MOD<<endl;
}