1. 程式人生 > >[題解]TopCoder SRM 625 div1 T1 PalindromePermutations

[題解]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;
}