【详细揭秘】多重集の交错排列

1. 题面

一个多重集,问相邻元素不相同的排列有多少个 .

2. 解法

Step 1. 容斥

为了方便,我们令这个多重集 Mm 中不同元素 a1,a2,,am,并且各出现 n1,n2,,nm 次 .

考虑容斥,于是问题变成求至少有 t 个同种元素构成的方案数 .

将「打破 t 个约束」看作「合并 t 个集合」的过程,即用捆绑法每次将两个元素合并成一个元素,那么合并之后看成多重集全排列即可 .

于是答案就是

b1,m,0bi<ni(nibi)!(nibi)!i=1m(1)bi(ni1bi)

Step 2. dp

转而枚举 nibi(下式中写为 ci),则柿子变为

c1,m,1cini(ci)!ci!i=1m(1)nici(ni1nici)

再令 s=ci,则柿子化成

c1,m,1cini(1)nss!ci!i=1m(ni1nici)

这个容斥系数 (1)nss! 都解决掉了,考虑计算后面的

1ci!i=1m(ni1nici)

我们枚举一个 ci,这样 ci 就有俩限制,于是可以背包 .

dpi,j 为考虑前 i 个元素,且 ci=j 的答案,则

dpi,j=k=1min(ni,j)dpi1,jk1k!(ni1nik)

非常标准的背包转移 .

预处理组合数,则可以 O(n2) 求解 .

3. [AHOI2018初中组]球球的排列

1. 题面

链接:https://www.luogu.com.cn/problem/P4448

给一个序列 {a},问有多少个排列 {p} 满足对于所有 2i<n,有 api1api+1 不为完全平方数 .

数据范围:ai109,n300 .

2. 解法

显然「完全平方」性质是有传递性的,即如果 xy,yz 均为完全平方数,则 xz 为完全平方数 .

所以说,就转换成了上面那个问题,但是要带标号 .

只需要把以前的柿子乘个阶乘即可( 里面多乘个 ni!,显然 dp 转移方程里也该乘 ni!).

这个题的代码:

using namespace std;
#define int long long // 去掉就 WA 一部分 qwq,我也不知道哪里忘改 long long 了 :( 
typedef long long ll;
const int N=333,MOD=1e9+7;
int n;
ll a[N],r[N],sz[N],dp[N],tdp[N],tmp[N],fac[N],ifac[N],inv[N],ans;
template<typename T>
inline T sqr(const T& t){return t*t;}
bool issqr(ll a){return sqr((int)(sqrt(a)))==a;}
ll C(int m,int n){return (m<n)?0:fac[m]*ifac[n]%MOD*ifac[m-n]%MOD;}
void initC(int n)
{
	fac[0]=ifac[0]=fac[1]=ifac[1]=inv[1]=1;
	for (int i=2;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%MOD;
		inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
		ifac[i]=ifac[i-1]*inv[i]%MOD;
	}
}
signed main()
{
	scanf("%lld",&n); initC(n);
	for (int i=1;i<=n;i++) scanf("%lld",a+i);
	for (int i=1;i<=n;i++) r[i]=i;
	for (int i=1;i<=n;i++)
	{
		++sz[r[i]];
		for (int j=i+1;j<=n;j++)
			if (issqr(a[i]*a[j])) r[j]=r[i];
	}
	dp[0]=1; ll tot=0;
	for (int i=1;i<=n;i++)
	{
		if (!sz[i]) continue;
		for (int j=0;j<=tot;j++){tmp[j]=dp[j]; dp[j]=0;}
		for (int j=0;j<sz[i];j++)
		{
			tdp[j]=ifac[sz[i]-j]*C(sz[i]-1,j)%MOD;
			if (j&1) tdp[j]=MOD-tdp[j];
			for (int k=0;k<=tot;k++) dp[j+k]=(dp[j+k]+tdp[j]*tmp[k])%MOD;
		} tot+=sz[i]-1;
	}
	for (int i=0;i<=tot;i++) ans=(ans+dp[i]*fac[n-i])%MOD;
	for (int i=1;i<=n;i++) ans=ans*fac[sz[i]]%MOD;
	printf("%lld\n",ans);
	return 0;
}
// 参考 https://www.luogu.com.cn/blog/skydogli/solution-p4448
posted @   yspm  阅读(181)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
😅​
点击右上角即可分享
微信分享提示