P4491 [HAOI2018]染色
出题人用心险恶啊。。。特意空格强调了“恰好”二字
如果恰 好出现了 \(S\) 次的颜色有 \(K\) 种
这题里有两个“恰好”,一个是恰好出现了 \(S\) 次,一个是恰好 \(K\) 种颜色。
这种一般都是转成至多至少反演或者容斥。
容斥掉的根本不是出题人强调的那个恰好。。。
一开始我就进坑了,不过还好没几分钟就跳了出来并想到了下面的做法
稍微想一想就知道我们应该算的是:\(f(k)=\rm 至少 k 种颜色恰好出现 S 次\) 的方案数。
然而其实会算重的,并不严格是方案数,只不过重复是可以计算的。事实上二项式反演好像都是这么干的,非常巧妙。
这个 \(f\) 非常好算。
首先选出 \(k\) 种颜色,钦定出现 \(S\) 次,其余乱选,这样必然至少 \(k\) 种颜色出现 \(S\) 次。
这样会钦定 \(Sk\) 个位置,那么直接选好即为 \(\binom{n}{Sk}\) 。同时这些元素可以互换位置,那么就是全排列除掉每一种颜色内部换顺序的情况。剩下 \(n-Sk\) 个位置,每一个位置都有 \(m-k\) 种颜色可选。
把分子那个二项式系数拆成阶乘,稍微化简一下得
设恰好选 \(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\}\) 的时候分别被计算一次。
所以:
二项式反演得
这个式子得想个办法卷起来,没法化了,而且看着很可卷的样子。
卷王能一眼看出这个怎么卷,我这种菜鸡只能慢慢试。。。
如果您是擅长卷可以直接跳过下面这部分。
组合数肯定得拆
令 \(A(i)=f(i)i!,B(i)=\dfrac{(-1)^{i}}{i!}\)
必然要翻转一个。翻转哪个好呢?试一试就知道应该翻转 \(A\) ,因为这样下标从 \(0\) 开始才能卷。
那么
总算可以卷了/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;
}