[AHOI2018初中组]球球的排列

题目

看了半天题解终于大概会了容斥做法。。。

前置知识:
较好的组合数学能力
容斥原理

首先有一个简单的结论,当 x ∗ y x*y xy x ∗ z x*z xz为完全平方数时,显然 y ∗ z y*z yz也为完全平方数。
于是可以简单地通过 O ( n 2 ) O(n^2) O(n2)暴力将这些球分块。
问题转化为:已知 n n n个球属于 m m m个颜色,同种颜色小球不能相邻,问排列方案数。

f ( s ) f(s) f(s)为至少有 s s s对同色小球相邻时的方案数,则由容斥原理知 a n s = ∑ i = 0 n f ( i ) ∗ ( − 1 ) i ans=\sum_{i=0}^nf(i)*(-1)^i ans=i=0nf(i)(1)i

考虑如何求 f ( s ) f(s) f(s)前,我们先假设我们将相邻相同颜色的球合并为一个块,设 b ( i ) b(i) b(i)为颜色为 i i i的球有多少个块(也就是说现在我们通过固定每一颜色的块数得到了一种情况),则可得当前情况的答案为
( ∑ i = 1 m b ( i ) ) ! ∏ i = 1 m b ( i ) ! ⋅ ∏ i = 1 m a ( i ) ! ⋅ C a ( i ) − 1 b ( i ) − 1 \frac{(\sum_{i=1}^{m}b(i))!}{\prod_{i=1}^mb(i)!}·\prod_{i=1}^{m}a(i)!·C_{a(i)-1}^{b(i)-1} i=1mb(i)!(i=1mb(i))!i=1ma(i)!Ca(i)1b(i)1
设有 s s s对同色小球相邻,则 ∑ i = 1 m b ( i ) = n − s \sum_{i=1}^{m}b(i)=n-s i=1mb(i)=ns
以两个乘号为分界将式子分为三段,第一段为 b b b的可重排列数,第二段为每一颜色内小球的全排列,第三段为每一颜色内对小球进行的分块方案(隔板法)

再转换一下可以变成
( n − s ) ! ∏ i = 1 m C a ( i ) − 1 b ( i ) − 1 ⋅ a ( i ) ! b ( i ) ! (n-s)!\prod_{i=1}^{m}\frac{C_{a(i)-1}^{b(i)-1}·a(i)!}{b(i)!} (ns)!i=1mb(i)!Ca(i)1b(i)1a(i)!
那么实际上 f ( s ) f(s) f(s)就是考虑了所有不同的 b ( i ) b(i) b(i)分配方法的总答案,我们只需要将上式后面这一坨所有的 b ( i ) b(i) b(i)情况所得到的结果相加再乘以 ( n − s ) ! (n-s)! (ns)!就得到了 f ( s ) f(s) f(s)

假设总共分成了 n − s n-s ns个块,我们可以把这些块分配到不同的颜色中去,其实也就是一个DP。
H = ∏ i = 1 m C a ( i ) − 1 b ( i ) − 1 ⋅ a ( i ) ! b ( i ) ! H=\prod_{i=1}^{m}\frac{C_{a(i)-1}^{b(i)-1}·a(i)!}{b(i)!} H=i=1mb(i)!Ca(i)1b(i)1a(i)!
则我们要求 ∑ H \sum H H

我们设 g [ i ] [ j ] g[i][j] g[i][j]表示考虑了前 i i i种颜色,至少有 j j j个块时,不同 b ( i ) b(i) b(i)所得到的 H H H的和,即 ∑ H \sum H H

状态方程是显然的
g [ i ] [ j ] = ∑ k = 1 m i n ( a ( i ) , j ) g [ i − 1 ] [ j − k ] ⋅ C a ( i ) − 1 k − 1 ⋅ a ( i ) ! k ! g[i][j]=\sum_{k=1}^{min(a(i),j)}g[i-1][j-k]·\frac{C_{a(i)-1}^{k-1}·a(i)!}{k!} g[i][j]=k=1min(a(i),j)g[i1][jk]k!Ca(i)1k1a(i)!

于是最终答案便是
∑ s = 0 n − 1 g [ m ] [ n − s ] ⋅ ( n − s ) ! ⋅ ( − 1 ) s \sum_{s=0}^{n-1}g[m][n-s]·(n-s)!·(-1)^s s=0n1g[m][ns](ns)!(1)s

完结撒花
PS:可以分析得到容斥做法复杂度为 O ( n 2 ) O(n^2) O(n2),真的好难,还是直接 O ( n 3 ) O(n^3) O(n3)DP好理解多了。

Code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<algorithm>
#define ll long long
using namespace std;
const ll INF=1e9+7;
const ll mod=1e9+7;
const ll maxn=305;
inline ll read()
{
	ll x=0,f=1;char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=x*10+(c-'0');
		c=getchar();
	}
	return x*f;
}
ll g[maxn][maxn];
ll n;
ll d[maxn],siz[maxn],vis[maxn],a[maxn],b[maxn],tot;
ll fac[maxn],C[maxn][maxn],inv[maxn],ans;
inline ll quickpow(ll x,ll tim)
{
	ll res=1;
	while(tim)
	{
		if(tim&1) res=res*x%mod;
		x=x*x%mod;
		tim>>=1;
	}
	return res%mod;
}
inline void init()
{
	fac[0]=1;C[0][0]=1;
	for(ll i=1;i<=300;i++) fac[i]=fac[i-1]*i%mod;
	for(ll i=1;i<=300;i++)
	{
		C[i][0]=1;C[i][i]=1;
		for(ll j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	}
	inv[300]=quickpow(fac[300],mod-2);
	for(ll i=299;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int main()
{
	init();
	n=read();
	for(ll i=1;i<=n;i++) d[i]=read(),siz[i]=1;
	for(ll i=1;i<=n;i++)
	{
		for(ll j=i-1;j>=1;j--)
		{
			if(vis[j]) continue;
			if((ll)sqrt(d[i]*d[j])*(ll)sqrt(d[i]*d[j])==d[i]*d[j])
			{
				vis[j]=1;
				siz[i]+=siz[j];
				break;
			}
		}
	}
	for(ll i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			a[++tot]=siz[i];
		}
	}
	g[0][0]=1;
	for(ll i=1;i<=tot;i++)
	{
		for(ll j=1;j<=n;j++)
		{
			ll len=min(a[i],j);
			for(ll k=1;k<=len;k++)
			{
				g[i][j]=(g[i][j]+g[i-1][j-k]*C[a[i]-1][k-1]%mod*fac[a[i]]%mod*inv[k]%mod)%mod;
			}
		}
	}
	for(ll i=0;i<n;i++)
	{
		ll flag=(i%2)?-1:1;
		ans+=g[tot][n-i]*fac[n-i]%mod*flag;
		ans%=mod;
	}
	ans=(ans+mod)%mod;
	cout<<ans<<endl;
	return 0;
} 
AHOI2001是一种用于处理模式匹配和字符串搜索的经典算法,全称为"Another Happy Odyssey in 2001"。它通常应用于构建高效、空间优化的KMP(Knuth-Morris-Pratt)算法的一种改进版本。这种有限自动机常用于处理字符串搜索问题,尤其是在处理大量文本数据时。 关于题目代码的具体内容,这通常涉及到编程竞赛或算法实现题。通常,你需要编写一段程序,包括定义一个有限状态机(Finite Automaton),处理输入字符串和模式串,并根据AHOI2001算法来查找模式是否在原字符串中。关键部分会涉及如何创建前缀函数表、动态规划和自适应策略。 由于这不是一个直接的答案,下面是一个简化版的代码框架示例(假设用Python): ```python class AhoCorasickAutomaton: def __init__(self, patterns): self.prefix_func = self.build_prefix_function(patterns) def build_prefix_function(self, patterns): # 建立前缀函数表的计算过程... pass def search(self, text): index = 0 for pattern in patterns: while index < len(text) and index + len(pattern) <= len(text): if self.match(text[index:], pattern): return True index += self.prefix_func[pattern] return False def match(self, text, pattern): # 匹配函数,比较两个字符串是否相等... pass # 使用示例: patterns = ['AB', 'AC'] # 输入模式列表 automaton = AhoCorasickAutomaton(patterns) text = 'ABCABCD' # 待搜索的字符串 if automaton.search(text): print("Pattern found") else: print("Pattern not found")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值