1. 程式人生 > 其它 >CF840C On the Bench 題解

CF840C On the Bench 題解

Codeforces
Luogu

Description.

給定序列 \(\{a_i\}\),求有多少個排列是好的。
一個排列 \(\{p_i\}\) 是好的,當且僅當

  • \(\forall i\in[2,n],a_{p_{i-1}}\times a_{p_i}\) 是完全平方數。

Solution.

首先肯定是容斥,求出欽定 \(k\) 對粘在一起的方案數。
前面的容斥係數是 \((-1)^k\),原因就考慮這個列舉本質是什麼。
本質是枚舉了一個集合,讓它裡面所有元素都不滿足,所以係數就是子集反演的係數。

然後平方數經典套路就是把平方因子去掉,接下來相同就是完全平方數。
那考慮對所有不同的數,它們無法粘在一起,貢獻可以拆開,合併是個揹包。

考慮對一種數,合併 \(j\) 個的貢獻是什麼,是 \(\dbinom{cnt_i-1}{j-1}\times \frac{cnt_i!}{j!}\)
推導就考慮先 \(cnt_i!\) 排列,然後格板 \(\dbinom{cnt_i-1}{j-1}\),然後考慮這 \(j\) 種本質相同可以交換除以 \(j!\)

Coding.

點選檢視程式碼
//是啊,你就是那隻鬼了,所以被你碰到以後,就輪到我變成鬼了{{{
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
template<typename T>inline void read(T &x)
{
	x=0;char c=getchar(),f=0;
	for(;c<48||c>57;c=getchar()) if(!(c^45)) f=1;
	for(;c>=48&&c<=57;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	f?x=-x:x;
}
template<typename T,typename...L>inline void read(T &x,L&...l) {read(x),read(l...);}//}}}
const int N=305,P=1e9+7;int n,a[N],cn[N],bs[N],dp[N],ut,ct[N],tn[N],tp[N];
int fc[N],fi[N],iv[N];//dbinit{{{
inline int ksm(int x,int q=P-2) {int r=1;for(;q;q>>=1,x=1ll*x*x%P) if(q&1) r=1ll*r*x%P;return r;}
inline void dbinit(int n=N-1)
{
	fc[0]=1;for(int i=1;i<=n;i++) fc[i]=1ll*fc[i-1]*i%P;
	iv[1]=1;for(int i=2;i<=n;i++) iv[i]=1ll*iv[P%i]*(P-P/i)%P;
	fi[0]=1;for(int i=1;i<=n;i++) fi[i]=1ll*fi[i-1]*iv[i]%P;
}
inline int C(int n,int m) {return n<0||m<0||n<m?0:1ll*fc[n]*fi[m]%P*fi[n-m]%P;}//}}}
const int V=31625;int pr[V],pc,ls[V],ph[V],mu[V];char pv[V];//prinit{{{
inline void prinit(int n=V-1)
{
	pv[1]=mu[1]=ph[1]=1,ls[1]=0;for(int i=1;i<=n;i++)
	{
		if(!pv[i]) pr[++pc]=i,mu[i]=-1,ph[i]=i-1,ls[i]=i;
		for(int j=1;j<=pc&&i*pr[j]<=n;j++)
		{
			ls[i*pr[j]]=pr[j],mu[i*pr[j]]=i%pr[j]?-mu[i]:0;
			ph[i*pr[j]]=ph[i]*(pr[j]-!!(i%pr[j]));
			pv[i*pr[j]]=1;if(i%pr[j]==0) break;
		}
	}
}//}}}
int main()
{
	prinit(),dbinit(),read(n);for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=n;i++)
	{
		int x=a[i];a[i]=1;for(int j=1;j<=pc&&pr[j]*pr[j]<=x;j++)
			{int c=1;while(x%pr[j]==0) c=pr[j]^1^c,x/=pr[j];a[i]*=c;}
		if(x^1) a[i]*=x;
	}//欽定 k 個相鄰,其他隨意的方案數,相鄰必須相同,相同綁在一起,揹包合併
	for(int i=1;i<=n;i++) tn[++ut]=a[i];
	sort(tn+1,tn+ut+1),ut=unique(tn+1,tn+ut+1)-tn-1;
	for(int i=1;i<=n;i++) cn[lower_bound(tn+1,tn+ut+1,a[i])-tn]++;
	dp[0]=1;int lim=0;
	for(int k=1;k<=ut;k++)
	{
		for(int i=0;i<cn[k];i++) bs[i]=1ll*C(cn[k]-1,i)*fc[cn[k]]%P*fi[cn[k]-i]%P;
		//printf("%d : \n",cn[k]);for(int i=0;i<cn[k];i++) printf("%d%c",bs[i],i==cn[k]-1?'\n':' ');
		for(int i=lim+cn[k]-1;i>=0;i--) tp[i]=0;
		for(int i=lim;i>=0;i--) for(int j=cn[k]-1;j>=0;j--) tp[i+j]=(tp[i+j]+1ll*dp[i]*bs[j])%P;
		lim+=cn[k]-1;for(int j=0;j<=lim;j++) dp[j]=tp[j];
	}
	for(int i=0;i<=lim;i++) dp[i]=1ll*dp[i]*fc[n-i]%P;
	//for(int i=0;i<=lim;i++) printf("%d%c",dp[i],i==lim?'\n':' ');
	int rs=0;for(int i=0;i<=lim;i++) rs=(rs+((i&1)?P-dp[i]:dp[i]))%P;
	return printf("%d\n",rs),0;
}