【CF840C】On the Bench-DP+組合數學

測試地址:On the Bench
題目大意: 給出一個長爲nn的序列AA,問有多少種11 ~ nn的排列pp,滿足對於任意1i<n1\le i<n,有APiAPi+1A_{P_i}\cdot A_{P_{i+1}}不爲完全平方數。
做法: 本題需要用到DP+組合數學。
直接狀壓DP的複雜度應該是O(n22n)O(n^22^n)的,肯定會爆,我們需要進一步發掘條件的性質。
我們發現兩個數乘積爲完全平方數這個性質有“傳遞性”,即若aba\cdot b爲完全平方數,bcb\cdot c爲完全平方數,則aca\cdot c也爲完全平方數。證明的話,我們只需要分開來看每個質因子的冪次的奇偶性即可,根據條件,aacc的各質因子的冪次應該關於模22分別同餘,而這也就意味着它們的冪次和一定爲偶數,所以結論成立。
於是現在問題就簡化爲,有tottot組數,共nn個數,要排成一排,同組數之間不能相鄰,問方案數。令第ii組數中數的個數爲numinum_i,我們考慮一組一組進行轉移。
我們先忽略它們在最後整個排列中的絕對位置,只考慮它們目前的相對位置,即不考慮空格的存在。那麼令f(i,j)f(i,j)爲前ii組數組成的排列中,不合法的相鄰元素對數有jj對(以下簡稱爲不合法位置有jj個)的方案數,我們考慮f(i1,j)f(i-1,j)會對f(i,x)f(i,x)產生什麼影響。
考慮第ii組如何擺放。首先我們把這一組的數拆成kk個連續段,這樣這一組數內部會產生numiknum_i-k個不合法位置,這一步的話,拆分有Cnumi1k1C_{num_i-1}^{k-1}種方案,而同組數內順序可以互換,不會產生其他影響,因此還要乘上一個numi!num_i!。拆完後,要把這kk段插入到序列中,此時會對原來序列中的不合法位置產生一些影響。令插入到原來序列中不合法位置的段數爲ll,顯然插入後不合法位置就會減少ll,而這樣插入的方案數應該爲:CjlClast+1jklC_j^l\cdot C_{last+1-j}^{k-l}lastlast表示前i1i-1組數中元素數量之和,也就是插入前序列的長度,那麼上式應該就非常明顯了:在jj個不合法位置中選ll個插入,剩下的在合法位置選一些位置插入,因此就是兩個組合數的乘積。於是在枚舉k,lk,l的基礎上,我們得到了f(i1,j)f(i-1,j)的貢獻:
f(i1,j)numi!Cnumi1k1CjlClast+1jklf(i-1,j)\cdot num_i!\cdot C_{num_i-1}^{k-1}\cdot C_j^l\cdot C_{last+1-j}^{k-l}
而進行完這一些操作後,不合法位置數目變爲j+numiklj+num_i-k-l,因此這個貢獻應該累加在f(i,j+numikl)f(i,j+num_i-k-l)中。於是最後的答案就是f(tot,0)f(tot,0)了。
上面的轉移方程看上去是O(n4)O(n^4)的(i,j,k,li,j,k,l),但因爲kk枚舉的範圍只到numinum_i,因此實際上時間複雜度爲O(n3)O(n^3),可以通過此題。
以下是本人代碼:

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章