题解 P4491 【[HAOI2018]染色】

题解 - P 4491 \mathrm{P4491} P4491

题目意思

题目传送门

S o l \mathrm{Sol} Sol

  • 二项式反演题

我们按套路设

f i f_{i} fi 表示钦定有 i i i 个颜色数为 S S S 的方案数。 g i g_i gi 表示恰好有 i i i 个颜色数为 S S S

那么 a n s = ∑ i = 0 m g i × w i ans=\sum_{i=0}^{m} g_i\times w_i ans=i=0mgi×wi

g i = ∑ j = i min ⁡ ( m , n s ) f j × C j i × ( − 1 ) j − i g_i=\sum_{j=i}^{\min(m,\frac{n}{s})} f_j\times C_{j}^{i}\times (-1)^{j-i} gi=j=imin(m,sn)fj×Cji×(1)ji

关键怎么求这个 f j f_j fj

f j = C m j × C n j × s × ( j × s ) ! ( s ! ) j × ( m − j ) n − j × s f_j=C_{m}^{j}\times C_n^{j\times s}\times \frac{(j\times s)!}{(s!)^j}\times (m-j)^{n-j\times s} fj=Cmj×Cnj×s×(s!)j(j×s)!×(mj)nj×s

中间那一项可以这样子理解:你先要在 n n n 个位置中至少选出 j × s j\times s j×s 个位置的方案数为 C n j × s C_n^{j\times s} Cnj×s ,对于具体地填入可以理解为先按照全排列的方式填再去掉相同颜色的那些多余情况,即 ( j × s ) ! ( s ! ) j \frac{(j\times s)!}{(s!)^j} (s!)j(j×s)!。其他都很好理解。

时间复杂度: O ( m 2 ) O(m^2) O(m2) 由于跑不满可以过 m ≤ 5 × 1 0 4 m\leq 5\times 10^4 m5×104 的点

然后我们考虑优化是这个式子:

原式: g i × i ! = ∑ j = i n ( − 1 ) j − i ( i − j ) ! j ! f j g_i\times i!=\sum_{j=i}^{n}\frac{(-1)^{j-i}}{(i-j)!}j!f_j gi×i!=j=in(ij)!(1)jij!fj

这就是道很经典的题目了,可以看看 Z J O I 2014 ZJOI2014 ZJOI2014 力那道题目

我们设 F i = i ! f i F_i=i!f_i Fi=i!fi G i = ( − 1 ) i i ! G_i=\frac{(-1)^i}{i!} Gi=i!(1)i

那么原式可以写成: g i = 1 i ! ∑ j = i n F j × G j − i g_i=\frac{1}{i!}\sum_{j=i}^{n} F_j\times G_{j-i} gi=i!1j=inFj×Gji

直接 N T T NTT NTT 一下就好了。

C o d e \mathrm{Code} Code

#include 
#define For(i,a,b) for ( int i=(a);i<=(b);i++ )
#define Dow(i,b,a) for ( int i=(b);i>=(a);i-- )
#define GO(i,x) for ( int i=head[x];i;i=e[i].nex )
#define mem(x,s) memset(x,s,sizeof(x))
#define cpy(x,s) memcpy(x,s,sizeof(x))
#define YES return puts("YES"),0
#define NO return puts("NO"),0
#define GG return puts("-1"),0
#define pb push_back
#define int long long
using namespace std;

inline int read()
{
	int sum=0,ff=1; char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') ff=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		sum=sum*10+(ch^48),ch=getchar();
	return sum*ff;
}

const int mod=1e9+7;
const int mo=1004535809;
const int N=1e7+5;
const int G=3ll;

int n,m,fac[N],inv[N],F[N],f[N],g[N];
int w[N],ans,l,r[N],mx,INV,lim,s;

inline int ksm(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1ll) ans=ans*a%mo;
		a=a*a%mo;
		b>>=1ll;
	}
	return ans%mo;
}

const int InvG=ksm(G,mo-2);
 
inline int C(int n,int m)
{
	return fac[n]*inv[m]%mo*inv[n-m]%mo;
}

inline void NTT(int *f,int op)
{
	For(i,0,lim-1) if(i<r[i]) swap(f[i],f[r[i]]);
	for ( int mid=1;mid<lim;mid<<=1ll)
	{
		int tmp=ksm(G,(mo-1)/(mid*2ll));
		if(op) tmp=ksm(tmp,mo-2);
		for ( int i=0;i<lim;i+=mid*2ll )
		{
			int w=1ll;
			for ( int j=0;j<mid;j++,w=w*tmp%mo )
			{
				int x=f[i+j];
				int y=w*f[i+j+mid]%mo;
				f[i+j]=(x+y)%mo;
				f[i+j+mid]=(x-y+mo)%mo;
			}
		}
	}
	if(op) For(i,0,lim-1) f[i]=f[i]*INV%mo;
}

signed main()
{
	n=read(),m=read(),s=read();
	For(i,0,m) w[i]=read();
	int M=max(n,m);
	fac[0]=1;
	For(i,1,M) fac[i]=fac[i-1]*i%mo;
	inv[M]=ksm(fac[M],mo-2)%mo;
	Dow(i,M-1,0) inv[i]=inv[i+1]*(i+1)%mo;
	mx=min(m,n/s);
	Dow(i,mx,0) F[i]=C(m,i)*fac[n]%mo*inv[n-i*s]%mo*ksm(ksm(fac[s],i),mo-2)%mo*ksm(m-i,n-i*s)%mo;
	For(i,0,mx) f[i]=F[i]*fac[i]%mo;
	For(i,0,mx) g[i]=((i&1ll)?mo-inv[i]:inv[i]);
	reverse(f,f+mx+1);
	lim=1ll;
	while(lim<=(mx+1)<<1ll) lim<<=1ll,l++;
	INV=ksm(lim,mo-2)%mo;
	For(i,0,lim-1) r[i]=r[i>>1ll]>>1ll|((i&1ll)<<(l-1));
	NTT(f,0),NTT(g,0);
	For(i,0,lim-1) f[i]=f[i]*g[i]%mo;
	NTT(f,1);
	int ans=0;
	For(i,0,mx) ans=(ans+1ll*f[mx-i]*w[i]%mo*inv[i]%mo)%mo;
	printf("%lld\n",ans);
	return 0;
}

你可能感兴趣的:(多项式,各省省选,二项式反演)