【CF840C】On the Bench-DP+组合数学

测试地址:On the Bench
题目大意: 给出一个长为 n n n的序列 A A A,问有多少种 1 1 1 ~ n n n的排列 p p p,满足对于任意 1 ≤ i &lt; n 1\le i&lt;n 1i<n,有 A P i ⋅ A P i + 1 A_{P_i}\cdot A_{P_{i+1}} APiAPi+1不为完全平方数。
做法: 本题需要用到DP+组合数学。
直接状压DP的复杂度应该是 O ( n 2 2 n ) O(n^22^n) O(n22n)的,肯定会爆,我们需要进一步发掘条件的性质。
我们发现两个数乘积为完全平方数这个性质有“传递性”,即若 a ⋅ b a\cdot b ab为完全平方数, b ⋅ c b\cdot c bc为完全平方数,则 a ⋅ c a\cdot c ac也为完全平方数。证明的话,我们只需要分开来看每个质因子的幂次的奇偶性即可,根据条件, a a a c c c的各质因子的幂次应该关于模 2 2 2分别同余,而这也就意味着它们的幂次和一定为偶数,所以结论成立。
于是现在问题就简化为,有 t o t tot tot组数,共 n n n个数,要排成一排,同组数之间不能相邻,问方案数。令第 i i i组数中数的个数为 n u m i num_i numi,我们考虑一组一组进行转移。
我们先忽略它们在最后整个排列中的绝对位置,只考虑它们目前的相对位置,即不考虑空格的存在。那么令 f ( i , j ) f(i,j) f(i,j)为前 i i i组数组成的排列中,不合法的相邻元素对数有 j j j对(以下简称为不合法位置有 j j j个)的方案数,我们考虑 f ( i − 1 , j ) f(i-1,j) f(i1,j)会对 f ( i , x ) f(i,x) f(i,x)产生什么影响。
考虑第 i i i组如何摆放。首先我们把这一组的数拆成 k k k个连续段,这样这一组数内部会产生 n u m i − k num_i-k numik个不合法位置,这一步的话,拆分有 C n u m i − 1 k − 1 C_{num_i-1}^{k-1} Cnumi1k1种方案,而同组数内顺序可以互换,不会产生其他影响,因此还要乘上一个 n u m i ! num_i! numi!。拆完后,要把这 k k k段插入到序列中,此时会对原来序列中的不合法位置产生一些影响。令插入到原来序列中不合法位置的段数为 l l l,显然插入后不合法位置就会减少 l l l,而这样插入的方案数应该为: C j l ⋅ C l a s t + 1 − j k − l C_j^l\cdot C_{last+1-j}^{k-l} CjlClast+1jkl l a s t last last表示前 i − 1 i-1 i1组数中元素数量之和,也就是插入前序列的长度,那么上式应该就非常明显了:在 j j j个不合法位置中选 l l l个插入,剩下的在合法位置选一些位置插入,因此就是两个组合数的乘积。于是在枚举 k , l k,l k,l的基础上,我们得到了 f ( i − 1 , j ) f(i-1,j) f(i1,j)的贡献:
f ( i − 1 , j ) ⋅ n u m i ! ⋅ C n u m i − 1 k − 1 ⋅ C j l ⋅ C l a s t + 1 − j k − l f(i-1,j)\cdot num_i!\cdot C_{num_i-1}^{k-1}\cdot C_j^l\cdot C_{last+1-j}^{k-l} f(i1,j)numi!Cnumi1k1CjlClast+1jkl
而进行完这一些操作后,不合法位置数目变为 j + n u m i − k − l j+num_i-k-l j+numikl,因此这个贡献应该累加在 f ( i , j + n u m i − k − l ) f(i,j+num_i-k-l) f(i,j+numikl)中。于是最后的答案就是 f ( t o t , 0 ) f(tot,0) f(tot,0)了。
上面的转移方程看上去是 O ( n 4 ) O(n^4) O(n4)的( i , j , k , l i,j,k,l i,j,k,l),但因为 k k k枚举的范围只到 n u m i num_i numi,因此实际上时间复杂度为 O ( n 3 ) O(n^3) O(n3),可以通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int n,num[310],sum[310],tot=0,belong[310];
ll a[310],f[310][310],fac[310],C[310][310];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		belong[i]=i;
		scanf("%lld",&a[i]); 
	}
	
	for(int i=1;i<=n;i++)
		if (belong[i]==i)
		{
			num[++tot]=1;
			for(int j=i+1;j<=n;j++)
			{
				ll s=(ll)sqrt((double)(a[i]*a[j])+0.5);
				if (s*s==a[i]*a[j]) belong[j]=i,num[tot]++;
			}
			sum[tot]=sum[tot-1]+num[tot];
		}
	
	fac[0]=1;
	for(ll i=1;i<=n;i++)
		fac[i]=fac[i-1]*i%mod;
	C[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	
	f[0][0]=1;
	for(int i=1;i<=tot;i++)
	{
		for(int j=0;j<=max(0,sum[i-1]-1);j++)
			for(int k=1;k<=min(num[i],sum[i-1]+1);k++)
				for(int l=0;l<=min(j,k);l++)
				{
					ll nxt=f[i-1][j]*fac[num[i]]%mod;
					nxt=nxt*C[num[i]-1][k-1]%mod;
					nxt=nxt*C[j][l]%mod;
					if (k-l>sum[i-1]+1-j) continue;
					nxt=nxt*C[sum[i-1]+1-j][k-l]%mod;
					f[i][j+num[i]-k-l]=(f[i][j+num[i]-k-l]+nxt)%mod;
				}
	}
	printf("%lld",f[tot][0]);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值