【codechef】交换字符串S的两个位置上的字符,求有多少对AB不相似

有一种操作,是交换字符串的两个位置上的字符(位置可以一样)。对于两个字符串A、B,如果分别对它们做这个操作后得出一样字符串C,那么称AB相似。

现在给出一个字符串S,A和B分别都是它的全排列里的一种。现在求有多少对A、B满足AB不相似。

//相似:A变化成C,B也变化成C,那么AB相似。那么不妨设B不动,A变化两次是否能到B。 
//假设B是随便取的,A是顺着B取的,那么B在取每一种排列时都有某几种A的排列要避开它的。而这些避开它的A也不用胡乱取,它只会和当前这一种B的排列很像。
//下面做法还有一个妙处是:它是按26个字母来分的。这样就不用考虑长度了 
#include <bits/stdc++.h>
using namespace std;
const int MOD=1000000007;
int fact[100001];
char S[100001];
int cnt[26];
int powmod(int a, int b){
    int ret=1;
    for(; b>0; b/=2){
        if(b&1)
            ret=1LL*ret*a%MOD;
        a=1LL*a*a%MOD;
    }
    return ret;
} 
int inv(int x){
    return powmod(x, MOD-2);
}
int C2(int x){
    return 1LL*x*(x-1)/2%MOD;
} 
void _main(){
    memset(cnt, 0, sizeof cnt);
    scanf("%s", S);
    int N=strlen(S);
    for(int i=0; i<N; i++)
        cnt[S[i]-'a']++;
    int total=fact[N];
    for(int i=0; i<26; i++)
        total=1LL*total*inv(fact[cnt[i]])%MOD; //字符串排列总数
    int sub=1;   //1是指AB完全一样的情况 
    for(int i=0; i<26; i++)
        for(int j=i+1; j<26; j++)
            sub=(sub+1LL*cnt[i]*cnt[j])%MOD; //和B比起来只有某两位不一样的互换了 
     for(int i=0; i<26; i++)
        for(int j=i+1; j<26; j++)
            for(int k=j+1; k<26; k++)
                sub=(sub+2LL*cnt[i]*cnt[j]%MOD*cnt[k])%MOD; //和B比起来只有某三位不一样的互换了,可以ab,ac也可以ac,ab,所以*2 
	for(int i=0; i<26; i++)
        for(int j=i+1; j<26; j++)
            for(int k=j+1; k<26; k++)
                for(int l=k+1; l<26; l++)
                    sub=(sub+3LL*cnt[i]*cnt[j]%MOD*cnt[k]%MOD*cnt[l])%MOD; //和B比起来只有某四位不一样的互换了  
	for(int i=0; i<26; i++)
        for(int j=i+1; j<26; j++)
            sub=(sub+1LL*C2(cnt[i])*C2(cnt[j]))%MOD;  //取两对分别同样的字母,交叉互换(某四位不一样) 
    for(int i=0; i<26; i++)
        for(int j=0; j<26; j++) if(j!=i)
            for(int k=j+1; k<26; k++) if(k!=i)
                sub=(sub+2LL*C2(cnt[i])*cnt[j]%MOD*cnt[k])%MOD; //取【一对同样字母】和【其它两个不同的字母】互换位置,因为其它两个不同字母换到那一对的位置上有两种换法,所以*2(某四位不一样)  
    int ans=1LL*(total-sub+MOD)*total%MOD; //假设B是随便取的,A是顺着B取的 
    printf("%d\n", ans);
}
 
int main(){
    fact[0]=1;
    for(int i=1; i<=100000; i++)
        fact[i]=1LL*fact[i-1]*i%MOD;
    int TEST;
    scanf("%d", &TEST);
    while(TEST--)
        _main();
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值