測試地址:On the Bench
題目大意: 給出一個長爲的序列,問有多少種 ~ 的排列,滿足對於任意,有不爲完全平方數。
做法: 本題需要用到DP+組合數學。
直接狀壓DP的複雜度應該是的,肯定會爆,我們需要進一步發掘條件的性質。
我們發現兩個數乘積爲完全平方數這個性質有“傳遞性”,即若爲完全平方數,爲完全平方數,則也爲完全平方數。證明的話,我們只需要分開來看每個質因子的冪次的奇偶性即可,根據條件,和的各質因子的冪次應該關於模分別同餘,而這也就意味着它們的冪次和一定爲偶數,所以結論成立。
於是現在問題就簡化爲,有組數,共個數,要排成一排,同組數之間不能相鄰,問方案數。令第組數中數的個數爲,我們考慮一組一組進行轉移。
我們先忽略它們在最後整個排列中的絕對位置,只考慮它們目前的相對位置,即不考慮空格的存在。那麼令爲前組數組成的排列中,不合法的相鄰元素對數有對(以下簡稱爲不合法位置有個)的方案數,我們考慮會對產生什麼影響。
考慮第組如何擺放。首先我們把這一組的數拆成個連續段,這樣這一組數內部會產生個不合法位置,這一步的話,拆分有種方案,而同組數內順序可以互換,不會產生其他影響,因此還要乘上一個。拆完後,要把這段插入到序列中,此時會對原來序列中的不合法位置產生一些影響。令插入到原來序列中不合法位置的段數爲,顯然插入後不合法位置就會減少,而這樣插入的方案數應該爲:,表示前組數中元素數量之和,也就是插入前序列的長度,那麼上式應該就非常明顯了:在個不合法位置中選個插入,剩下的在合法位置選一些位置插入,因此就是兩個組合數的乘積。於是在枚舉的基礎上,我們得到了的貢獻:
而進行完這一些操作後,不合法位置數目變爲,因此這個貢獻應該累加在中。於是最後的答案就是了。
上面的轉移方程看上去是的(),但因爲枚舉的範圍只到,因此實際上時間複雜度爲,可以通過此題。
以下是本人代碼:
#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;
}