题解:P5684 [CSP-J2019 江西] 非回文串

传送门

一道很简单的数学题。。。

题目简化:给定一个字符串,通过交换顺序使它不是一个回文串。

看上去很简单,用一个大模拟就行了。

数据: 3 ≤ n ≤ 2000 3\le n\le 2000 3n2000

如果直接用模拟的话,时间复杂度就是 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=126(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=126(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=126(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,欢迎大家来找我!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值