2019.02.28【HAOI2018】【BZOJ5302】【洛谷P4495】染色(容斥原理)(NTT)

BZOJ传送门

洛谷传送门


解析:

N = min ⁡ ( ⌊ n s ⌋ , m ) N=\min(\lfloor\frac{n}{s}\rfloor,m) N=min(sn,m),显然最多只可能有 N N N种颜色恰好被染成 s s s块。

考虑将恰好 k k k种被染成 s s s块的颜色转化成至少 k k k种被染成 s s s块的颜色来容斥。

设这个数为 f k f_k fk,现在考虑凑出 f k f_k fk

首先选择 i i i种颜色 ( m i ) {m\choose i} (im),然后选择 i s is is个位置 ( n i s ) {n\choose is} (isn),在这 i s is is个位置上做可重集的全排列 ( i s ) ! ( s ! ) i \frac{(is)!}{(s!)^i} (s!)i(is)!,然后剩下的 n − i s n-is nis个位置每一个有 m − i m-i mi种颜色可以选择。

我们得到 f i = ( m i ) ( n i s ) ( i s ) ! ( s ! ) i ( n − i s ) m − i f_i={m\choose i}{n\choose is}\frac{(is)!}{(s!)^i}(n-is)^{m-i} fi=(im)(isn)(s!)i(is)!(nis)mi

然后常规容斥:

f i = ∑ j = i N a n s j a n s i = ∑ j = i N ( − 1 ) j − i ( j i ) f j f_i=\sum_{j=i}^Nans_j\\ans_i=\sum_{j=i}^N(-1)^{j-i}{j\choose i}f_j fi=j=iNansjansi=j=iN(1)ji(ij)fj

然后展开:
a n s i = ∑ j = i N ( − 1 ) j − i j ! ( j − i ) ! i ! f j a n s i ∗ i ! = ∑ j = i N ( − 1 ) j − i ( j − i ) ! f j j ! \begin{aligned} ans_i&=&&\sum_{j=i}^N(-1)^{j-i}\frac{j!}{(j-i)!i!}f_j\\ ans_i*i!&=&&\sum_{j=i}^N\frac{(-1)^{j-i}}{(j-i)!}f_jj! \end{aligned} ansiansii!==j=iN(1)ji(ji)!i!j!fjj=iN(ji)!(1)jifjj!

F ( x ) = ∑ ( − 1 ) i i ! x i , G ( x ) = ∑ f j j ! F(x)=\sum \frac{(-1)^i}{i!}x^i,G(x)=\sum f_jj! F(x)=i!(1)ixi,G(x)=fjj!

我们将 G G G翻转一下就可以做卷积了。

然后将结果数组翻转就可以和原数组 w w w做点值乘法得到答案了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

cs int mod=1004535809;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
inline int quickpow(int a,int b,int res=1){for(;b;b>>=1,a=mul(a,a))if(b&1)res=mul(res,a);return res;}

inline void NTT(int *A,int len,int typ){
	static int r[1<<18];
	for(int re i=0;i<len;++i)r[i]=r[i>>1]>>1|((i&1)*len>>1);
	for(int re i=0;i<len;++i)if(i<r[i])swap(A[i],A[r[i]]);
	for(int re i=1;i<len;i<<=1){
		re int wn=quickpow(typ==1?3:(mod+1)/3,(mod-1)/i/2);
		for(int re j=0;j<len;j+=i<<1){
			int w=1;
			for(int re k=0;k<i;++k,w=mul(w,wn)){
				int re x=A[j+k],y=mul(w,A[j+k+i]);
				A[j+k]=add(x,y);
				A[j+k+i]=dec(x,y);
			}
		}
	}
	if(typ==-1)for(int re i=0,inv=quickpow(len,mod-2);i<len;++i)A[i]=mul(A[i],inv);
}

int F[1<<18],G[1<<18];

cs int M=100005;
cs int P=1e7+7;
int n,m,N,S;
int w[M];
int fac[P],inv[P],ifac[P];
int f[M];

inline int C(int n,int m){
	return mul(fac[n],mul(ifac[m],ifac[n-m]));
}

signed main(){
	n=getint(),m=getint(),S=getint();
	N=min(n/S,m);
	for(int re i=0;i<=m;++i)w[i]=getint();
	fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=ifac[1]=1;
	for(int re i=2;i<=max(n,m);++i){
		fac[i]=mul(fac[i-1],i);
		inv[i]=mul(mod-mod/i,inv[mod%i]);
		ifac[i]=mul(ifac[i-1],inv[i]);
	}
	for(int re i=0,coef=1;i<=N;++i,coef=mul(coef,ifac[S]))f[i]=mul(mul(C(m,i),C(n,S*i)),mul(quickpow(m-i,n-i*S),mul(fac[i*S],coef)));
	for(int re i=0;i<=N;++i){
		F[i]=mul(ifac[i],(i&1)?mod-1:1);
		G[i]=mul(f[i],fac[i]);
	}
	reverse(G,G+N+1);
	int len=1;
	while(len<=(N<<1))len<<=1;
	NTT(F,len,1);
	NTT(G,len,1);
	for(int re i=0;i<len;++i)F[i]=mul(F[i],G[i]);
	NTT(F,len,-1);
	reverse(F,F+N+1);
	int ans=0;
	for(int re i=0;i<=N;++i)ans=add(ans,mul(w[i],mul(F[i],ifac[i])));
	cout<<ans<<'\n';
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值