[題解]TopCoder SRM 625 div1 T1 PalindromePermutations
阿新 • • 發佈:2018-11-06
哇塞今天居然做出來了兩道TC哎~(≧▽≦)/~(好像這是什麼值得高興的事情一樣)
題目描述
給出一個字串s,問把s中的字元打亂後得到迴文串的概率是多少(|s|<=50)
分析
哎呀又是自己想出來的可開心啦
首先我們不考慮精度問題,先計算出打亂之後的所有可能字串的數量:
設第i個字母在s中出現的次數為vis[i],s的長度為len。考慮把這些字母放進len個空位中
1.按照字典序列舉,先放’a’,如果vis[1]>0,說明原字串中有’a’,那麼我們現在要在len個空位中選擇vis[1]個放上’a’,方案數為
,剩下的空位數為len-vis[1]
2. 當我們要接著放第x個字母時,要在len-vis[1]個空位中選vis[x]個放上字母,方案數為
3. 以此類推,設列舉到第i個字元時剩下的空位數為left[i],那麼所有可能的字串數
接著,我們要計算迴文串的數量
很重要的一點就是:記有x個字元出現了奇數次,如果x>1,那麼這個字串不論怎麼打亂都不可能是迴文串。為什麼呢?
我們按照原字串長度len的奇偶性把題目分成兩種情況:
- len為奇數: 在最中間的地方放上某一個字元p,然後在左右兩邊放上其他字母。
- len為偶數: 直接在左右兩邊放字母
由於迴文串是左右對稱的,所以只要知道左邊的排列,右邊的排列也就固定了。所以,假設字元m在左邊出現了k次,那麼:
- m!=p 它在整個字串中出現的總次數為k*2=vis[m],vis[m]一定是偶數
- 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;
}