1. 程式人生 > 實用技巧 >P4491 [HAOI2018]染色

P4491 [HAOI2018]染色

P4491 [HAOI2018]染色

出題人用心險惡啊。。。特意空格強調了“恰好”二字

如果恰 好出現了 \(S\) 次的顏色有 \(K\)

這題裡有兩個“恰好”,一個是恰好出現了 \(S\) 次,一個是恰好 \(K\) 種顏色。

這種一般都是轉成至多至少反演或者容斥。

容斥掉的根本不是出題人強調的那個恰好。。。

一開始我就進坑了,不過還好沒幾分鐘跳了出來就想到了下面的做法

稍微想一想就知道我們應該算的是:\(f(k)=\rm 至少 k 種顏色恰好出現 S 次\) 的方案數。

然而其實會算重的,並不嚴格是方案數,只不過重複是可以計算的。事實上二項式反演好像都是這麼幹的,非常巧妙。

這個 \(f\)

非常好算。

\[f(k)=\binom{m}{k}\dfrac{\binom{n}{Sk}(Sk)!}{(S!)^{k}}(m-k)^{n-Sk} \]

首先選出 \(k\) 種顏色,欽定出現 \(S\) 次,其餘亂選,這樣必然至少 \(k\) 種顏色出現 \(S\) 次。

這樣會欽定 \(Sk\) 個位置,那麼直接選好即為 \(\binom{n}{Sk}\) 。同時這些元素可以互換位置,那麼就是全排列除掉每一種顏色內部換順序的情況。剩下 \(n-Sk\) 個位置,每一個位置都有 \(m-k\) 種顏色可選。

把分子那個二項式係數拆成階乘,稍微化簡一下得

\[f(k)=\binom{m}{k}\dfrac{n!}{(S!)^{k}(n-Sk)!}(m-k)^{n-Sk} \]

設恰好選 \(k\) 種顏色的方案數設為 \(g(k)\)\(up=\min(m,\lfloor\dfrac{n}{S}\rfloor)\) 是選的顏色種數上界。

這個 \(g\) 是真的方案數了

再來說上面插的那句話

然而其實會算重的,並不嚴格是方案數

(這個其實是翻command_block的blog時發現的問題,剛開始學二項式反演被那個錯誤的叫法迷惑了好久,到現在才解決,雖然記個式子就能做題了。向cmd表示感謝。)

那麼究竟重複了多少呢?

首先肯定,重複的部分是亂選與欽定出現了相同的方案。

那麼對於 \(i(i\ge k)\) 種顏色相同,會在欽定 \(k\) 種相同時重複 \(\dbinom{i}{k}\)

次。

比如 \(i=4,k=2\)\(\{1,2,3,4\}\) 這個集合會在欽定 \(\{1,2\},\{1,3\},\{1,4\},\{2,3\},\{2,4\},\{3,4\}\) 的時候分別被計算一次。

所以:

\[f(k)=\sum_{i=k}^{up}\binom{i}{k}g(i) \]

二項式反演得

\[g(k)=\sum_{i=k}^{up}(-1)^{i-k}\binom{i}{k}f(i) \]

這個式子得想個辦法捲起來,沒法化了,而且看著很可卷的樣子。

卷王能一眼看出這個怎麼卷,我這種菜雞隻能慢慢試。。。

如果您是卷王可以直接跳過下面這部分。

組合數肯定得拆

\[g(k)=\dfrac{1}{k!}\sum_{i=k}^{up}f(i)i!\dfrac{(-1)^{i-k}}{(i-k)!} \]

\(A(i)=f(i)i!,B(i)=\dfrac{(-1)^{i}}{i!}\)

\[g(k)=\dfrac{1}{k!}\sum_{i=k}^{up}A(i)B(i-k) \]

必然要翻轉一個。翻轉哪個好呢?試一試就知道應該翻轉 \(A\) ,因為這樣下標從 \(0\) 開始才能卷。

那麼

\[g(k)=\dfrac{1}{k!}\sum_{i=k}^{up}A'(up-i)B(i-k)\\ =\dfrac{1}{k!}\sum_{i=0}^{up-k}A'(up-i-k)B(i) \]

總算可以捲了/ll,卷完 \([x^k]g(x)\)\(up-k\) 處提取。

出題人挺良心的,樣例看著很強的樣子=_=

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mkp(x,y) make_pair(x,y)
#define pb(x) push_back(x)
#define sz(v) (int)v.size()
typedef long long LL;
typedef double db;
template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
#define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
#define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
	return f?x:-x;
}
#define mod 1004535809
const int V=10000005;
const int N=100005;
const int M=N<<2;

namespace math{
int fac[V],ifc[V];
inline int qpow(int n,int k){int res=1;for(;k;k>>=1,n=1ll*n*n%mod)if(k&1)res=1ll*n*res%mod;return res;}
inline void fmod(int&x){x-=mod,x+=x>>31&mod;}
int binom(int n,int m){return n<m?0:1ll*fac[n]*ifc[m]%mod*ifc[n-m]%mod;}
void initmath(const int&n=V-1){
	fac[0]=1;for(int i=1;i<=n;++i)fac[i]=1ll*i*fac[i-1]%mod;
	ifc[n]=qpow(fac[n],mod-2);for(int i=n-1;i>=0;--i)ifc[i]=1ll*ifc[i+1]*(i+1)%mod;
}

}
using math::qpow;
using math::fmod;

namespace poly{
int rev[M],lg,lim;
void poly_init(const int&n){
	for(lg=0,lim=1;lim<n;++lg,lim<<=1);
	for(int i=0;i<lim;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
}
void NTT(int*a,int op){
	for(int i=0;i<lim;++i)
		if(i>rev[i])swap(a[i],a[rev[i]]);
	const int g=op?3:qpow(3,mod-2);
	for(int i=1;i<lim;i<<=1){
		const int wn=qpow(g,(mod-1)/(i<<1));
		for(int j=0;j<lim;j+=i<<1){
			int w0=1;
			for(int k=0;k<i;++k,w0=1ll*w0*wn%mod){
				const int X=a[j+k],Y=1ll*a[i+j+k]*w0%mod;
				fmod(a[j+k]=X+Y),fmod(a[i+j+k]=mod+X-Y);
			}
		}
	}
	if(op)return;const int ilim=qpow(lim,mod-2);
	for(int i=0;i<lim;++i)a[i]=1ll*a[i]*ilim%mod;
}
#define clr(a,n) memset(a,0,sizeof(int)*(n))
#define cpy(a,b,n) memcpy(a,b,sizeof(int)*(n))
void poly_mul(int*f,int*g,int*ans,int n,int m){
	static int A[M],B[M];poly_init(n+m);
	cpy(A,f,n),clr(A+n,lim-n),NTT(A,1);
	cpy(B,g,m),clr(B+m,lim-m),NTT(B,1);
	for(int i=0;i<lim;++i)ans[i]=1ll*A[i]*B[i]%mod;
	NTT(ans,0);
}

}

int n,m,S,up,f[M],g[M],A[M],B[M],C[M],W[N],ans;
signed main(){
	math::initmath();
	n=read(),m=read(),S=read(),up=min(n/S,m);
	rep(i,0,m)W[i]=read();
	for(int i=0;i<=up;++i)f[i]=1ll*math::binom(m,i)*math::fac[n]%mod*qpow(math::ifc[S],i)%mod*math::ifc[n-S*i]%mod*qpow(m-i,n-S*i)%mod;
	for(int i=0;i<=up;++i)A[i]=1ll*math::fac[up-i]*f[up-i]%mod;
	for(int i=0;i<=up;++i)B[i]=(i&1)?mod-math::ifc[i]:math::ifc[i];
	poly::poly_mul(A,B,C,up+1,up+1);
	for(int i=0;i<=up;++i)g[i]=1ll*math::ifc[i]*C[up-i]%mod;
	for(int i=0;i<=up;++i)fmod(ans+=1ll*g[i]*W[i]%mod);
	printf("%d\n",ans);
	return 0;
}