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

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值