【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 ", 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; }
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。