Codeforces 895D Round#448 2D :组合数学+小学数学优化

来源:互联网 发布:边缘端口防环路 编辑:程序博客网 时间:2024/05/16 10:52

题意:给出串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;}


阅读全文
0 0