一道很简单的数学题。。。
题目简化:给定一个字符串,通过交换顺序使它不是一个回文串。
看上去很简单,用一个大模拟就行了。
数据: 3 ≤ n ≤ 2000 3\le n\le 2000 3≤n≤2000。
如果直接用模拟的话,时间复杂度就是 O ( n ! ) O(n!) O(n!),最坏情况下是 2000 ! 2000! 2000!。
注意看题目中的这几个字:交换顺序。这说明了这个字符串中的字符是不会改变的,而题目要求我们求有多少种情况使得这个字符串不是回文串。
与回文串相关,立马想到回文串的对称性!
所以第一步一定是先统计每个字符出现了多少次(这里建议用 map
)。
for(int i=1;i<=n;i++)
{
cin>>c[i];
mp[c[i]]++;//用mp来统计个数
}
因为回文串具有对称性,所以我们只需要考虑一边,另一边跟这边也就一样了。
直接求非回文串的个数太麻烦,这里用了正难则反的思路:我们只需要求出有多少个回文串,再用总的个数减掉就是答案了。
总的个数很好求,就是 n ! n! n!,那回文串的个数怎么求呢?
我们前面说过,考虑回文串只需要考虑一边就行了,所以说我们只需要将每种字符出现的次数 mp[c[i]]
除以
2
2
2 就可以了。由此,我们需要将 mp[c[i]]
分成两类。
当 mp[c[i]]
为偶数时
此时 mp[c[i]]
可以直接被分成两部分,总的
n
n
n 也一定是偶数。一边就有
n
2
\frac{n}{2}
2n 个字母,每个字母之间互相交换,一共有
n
2
!
\frac{n}{2}!
2n! 种情况。
现在我们又对于每个字母进行讨论。
因为字母之间是可以两两交换的,总的方案也就有 m p c i ! mp_{c_{i}}! mpci! 种但是同一边交换是没有用的啊,所以我们还要再这个基础上再去掉重复的,也就是同一边的搭配情况,算下来是 ( m p c i / 2 ) ! (mp_{c_{i}}/2)! (mpci/2)!。
把上面的公式合在一起,我们就得到了一个字母的排列情况,即。
m p c i ! ( m p c i ) / 2 \frac{mp_{c_{i}}!}{(mp_{c_{i}})/2} (mpci)/2mpci!
一共有 26 个字母,所以共有。
∏ i = 1 26 m p c i ! ( m p c i ) / 2 \prod_{i=1}^{26}\frac{mp_{c_{i}}!}{(mp_{c_{i}})/2} i=1∏26(mpci)/2mpci!
最后再把总共的排列情况乘上,所以最终的公式是。
n 2 ! × ∏ i = 1 26 m p c i ! ( m p c i ) / 2 \frac{n}{2}! \times \prod_{i=1}^{26} \frac{mp_{c_{i}}!}{(mp_{c_{i}})/2} 2n!×i=1∏26(mpci)/2mpci!
最后用 n ! n! n! 减去这么多就是答案了。
a n s = n ! − n 2 ! × ∏ i = 1 26 m p c i ! ( m p c i ) / 2 ans=n!-\frac{n}{2}! \times \prod_{i=1}^{26} \frac{mp_{c_{i}}!}{(mp_{c_{i}})/2} ans=n!−2n!×i=1∏26(mpci)/2mpci!
代码实现。
int jc(int x)//前半部分的阶乘
{
int z=1;
for(int i=2;i<=x;i++)
{
z*=i;
z%=mod;
}
return z;
}
int jc1(int x)//后半部分的阶乘
{
int z=1;
for(int i=x/2+1;i<=x;i++)
{
z*=i;
z%=mod;
}
return z;
}
int ou()//偶数求值
{
int sum=1;
for(int i=1;i<=m;i++)
{
sum*=jc1(mp[c[i]]);
sum%=mod;
}
sum=sum*jc(n/2)%mod;
return sum;
}
当 mp[c[i]]
为奇数时
跟上方原理相同,只需要去掉其中的一个值,再按照偶数的方法求出值就可以了。注意:中间的那个值是不定的,所以需要多乘一个 mp[c[i]]
自身的值。
代码如下。
int ji()
{
int sum=0;
for(int i=1;i<=m;i++)
{
if(mp[c[i]]&1)
{
mp[c[i]]--;
sum+=(mp[c[i]]+1)*ou()%mod;//将mp[c[i]]转化为偶数后按照偶数的方法求值
mp[c[i]]++;
}
}
return sum;
}
万事俱备,一交代码。
只得了 80 80 80 分。
原因则出在了奇数这里。
我们想想:如果你有两个字母的个数都是奇数,那你就根本没法构成回文串,此时的 a n s = 0 ans=0 ans=0。
所以,在计算之前,我们需要特判一下。
int sum=0;
for(int i=1;i<=m;i++)
{
if(mp[c[i]]&1)
{
sum++;
}
}
if(sum>1)
{
ans=0;
}
else
{
ans=ji();
}
完整代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
int n,m;
char c[2006];
map<char,int>mp;
int jc(int x)
{
int z=1;
for(int i=2;i<=x;i++)
{
z*=i;
z%=mod;
}
return z;
}
int jc1(int x)
{
int z=1;
for(int i=x/2+1;i<=x;i++)
{
z*=i;
z%=mod;
}
return z;
}
int ou()
{
int sum=1;
for(int i=1;i<=m;i++)
{
sum*=jc1(mp[c[i]]);
sum%=mod;
}
sum=sum*jc(n/2)%mod;
return sum;
}
int ji()
{
int sum=0;
for(int i=1;i<=m;i++)
{
if(mp[c[i]]&1)
{
mp[c[i]]--;
sum+=(mp[c[i]]+1)*ou()%mod;
mp[c[i]]++;
}
}
return sum;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>c[i];
mp[c[i]]++;
}
set<char>s;
for(int i=1;i<=n;i++)
{
s.insert(c[i]);
}
m=s.size();
for(int i=1;i<=m;i++)
{
c[i]=(*s.begin());
s.erase(s.begin());
}
bool flag=true;
for(int i=1;i<=m;i++)
{
if(mp[c[i]]&1)
{
flag=false;
}
}
int ans;
if(flag)
{
ans=ou();
}
else
{
int sum=0;
for(int i=1;i<=m;i++)
{
if(mp[c[i]]&1)
{
sum++;
}
}
if(sum>1)
{
ans=0;
}
else
{
ans=ji();
}
}
int t=jc(n)-ans;
cout<<(t<0?t+mod:t);
return 0;
}
我的洛谷号:cwxcplh,欢迎大家来找我!