[题解]TopCoder SRM 625 div1 T1 PalindromePermutations

哇塞今天居然做出来了两道TC哎~(≧▽≦)/~(好像这是什么值得高兴的事情一样

题目描述

给出一个字符串s,问把s中的字符打乱后得到回文串的概率是多少(|s|<=50)

分析

哎呀又是自己想出来的可开心啦


首先我们不考虑精度问题,先计算出打乱之后的所有可能字符串的数量:

设第i个字母在s中出现的次数为vis[i],s的长度为len。考虑把这些字母放进len个空位中

1.按照字典序枚举,先放’a’,如果vis[1]>0,说明原字符串中有’a’,那么我们现在要在len个空位中选择vis[1]个放上’a’,方案数为 C ( v i s [ a ] l e n ) ,剩下的空位数为len-vis[1]
2. 当我们要接着放第x个字母时,要在len-vis[1]个空位中选vis[x]个放上字母,方案数为 C ( v i s [ x ] l e n v i s [ 1 ] )
3. 以此类推,设枚举到第i个字符时剩下的空位数为left[i],那么所有可能的字符串数 t o t = C ( v i s [ i ] l e f t [ i ] ) ( 1 <= i <= 26 , v i s [ i ] > 0 )


接着,我们要计算回文串的数量

很重要的一点就是:记有x个字符出现了奇数次,如果x>1,那么这个字符串不论怎么打乱都不可能是回文串。为什么呢?

我们按照原字符串长度len的奇偶性把题目分成两种情况:

  1. len为奇数: 在最中间的地方放上某一个字符p,然后在左右两边放上其他字母。
  2. len为偶数: 直接在左右两边放字母

由于回文串是左右对称的,所以只要知道左边的排列,右边的排列也就固定了。所以,假设字符m在左边出现了k次,那么:

  1. m!=p 它在整个字符串中出现的总次数为k*2=vis[m],vis[m]一定是偶数
  2. m=p 它在整个字符串中出现的总次数为k*2+1=vis[m],vis[m]为奇数

因此,回文串中出现了奇数次的字符最多只有1个

那么,和之前同理,我们可以计算出回文串出现的方案数,然后除一下就可以了


最后,我们来讨论一下精度问题——显然,总方案数tot是会暴long long的,那么怎么么办呢?这里有一个十分神奇的操作叫做double,它居然可以存到308位。还有一种更加神奇的操作叫做long double,大概几千位都不是问题的吧,所以居然就这么水过了emmm

代码

//tc is healthy, just do it
#include <bits/stdc++.h>
const int N=55;
using namespace std;

template<class T> void checkmin(T &a,const T &b) { if (b<a) a=b; } 
template<class T> void checkmax(T &a,const T &b) { if (b>a) a=b; }

class PalindromePermutations {
public:
    double palindromeProbability( string word ) ;
};


int n,len,lef,vis[N],a[N],flag=0,cnt=0;
double c[N][N],ans=1,tot=1;

void Init(){
    for(int i=1;i<=n;i++)c[i][i]=c[i][0]=1,c[i][1]=i*1.0;
    for(int i=3;i<=n;i++)
      for(int j=2;j<i;j++)
        c[i][j]=c[i-1][j]+c[i-1][j-1];
}

double PalindromePermutations::palindromeProbability(string word) {
    n=word.length();
    Init();
    for(int i=0;i<n;i++)vis[word[i]-'a'+1]++;
    len=n;
    for(int i=1;i<=26;i++){
        if(!vis[i])continue;
        if(vis[i]&1)flag++,len=n-1;//注意是-1不是-vis[i],一开始就挂在这里了呢
        else cnt++,a[cnt]=vis[i]/2;
    }
    if(flag>1||len%2==1){
        ans=0;
        return ans;
    }
    lef=n;
    for(int i=1;i<=26;i++){
        if(!vis[i])continue;
        tot*=c[lef][vis[i]];
        lef-=vis[i];
    }//计算总方案数
    lef=len/2;
    for(int i=1;i<=cnt;i++){
        ans*=c[lef][a[i]];
        lef-=a[i];
    }//计算回文串数
    return ans/tot;
}

猜你喜欢

转载自blog.csdn.net/Scl1231/article/details/82228271