一、题目
二、解法
容斥是不难想到的,我的主要瓶颈在于算钦定有 x x x对魔术对的方案数。这里有两种思路,第一种是安排法(都是蒟蒻作者乱取的名字),也就是我们通过直接安排顺序来算方案数,第二种是插入法,也就是我们通过插入的方式使之满足条件,本题第一种方法应该是走不通的,让我们来康康第二种方法。
我们先把 n − x n-x n−x张牌随便乱放(插入不是在长度为 n n n的格子上填数,你可以理解为动态数组),所以这样的方案数是 ( n − x ) ! (n-x)! (n−x)!,但是还要考虑相同的之间的顺序就很麻烦,我们干脆把相同的牌也看做不同,这样最后除以 ∏ a i ! \prod a_i! ∏ai!就可以了。然后设 f ( i , j ) f(i,j) f(i,j)为考虑前 i i i种颜色,选了 j j j个魔术对的方案数,我们考虑转移。
其实算 f f f的过程相当于一个背包,考虑第 i i i种颜色贡献 b i b_i bi个魔术对( b i < a i b_i<a_i bi<ai)的方案数,第一张牌有 a i − b i a_i-b_i ai−bi个位置可以插入,第二张牌有 a i − b i + 1 a_i-b_i+1 ai−bi+1个位置可以插入 . . . . . . . ....... .......那么方案数就是 ( a i − 1 ) ! ( a i − b i − 1 ) ! \frac{(a_i-1)!}{(a_i-b_i-1)!} (ai−bi−1)!(ai−1)!,由于相同的牌不同,我们先要从 a i a_i ai中选出 b i b_i bi个构成魔术对的牌,方案数 C ( a i , b i ) C(a_i,b_i) C(ai,bi),把这两个乘起来就是背包的转移系数了。
外面套一个容斥: ∑ i = k n ( − 1 ) i − k × C ( i , k ) × f ( m , i ) × ( n − i ) ! \sum_{i=k}^n (-1)^{i-k}\times C(i,k)\times f(m,i)\times (n-i)! ∑i=kn(−1)i−k×C(i,k)×f(m,i)×(n−i)!,这就是二项式反演的常用形式,不懂的可以康康这篇博客。这样做的复杂度是 O ( n m ) O(nm) O(nm)的,发现背包的过程其实是卷积的过程,我们每次把最小的两项卷起来就行了,相当于一个启发式背包,这样做的时间复杂度是 O ( m log 2 n ) O(m\log^2 n) O(mlog2n)的。
作者并不会证明时间复杂度(找时间问问 j z m jzm jzm也许?),贴个代码。
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int N = 20005;
const int M = 100005;
const int MOD = 998244353;
#define int long long
int read()
{
int num=0,flag=1;
char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,k,ans,pr,lg,len,fac[M],inv[M],A[M*2],B[2*M],Rev[2*M];
vector<int> f[N];
struct node
{
int x,s;
bool operator < (const node &b) const
{
return s>b.s;
}
};
priority_queue<node> q;
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=2; i<=n; i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1; i<=n; i++) inv[i]=inv[i-1]*inv[i]%MOD;
for(int i=1; i<=n; i++) fac[i]=fac[i-1]*i%MOD;
}
int C(int n,int m)
{
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
void NTT(int *a,const int len,int tmp)
{
for(int i=0; i<len; i++)
{
Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
if(i<Rev[i])
swap(a[i],a[Rev[i]]);
}
for(int s=2; s<=len; s<<=1)
{
int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
for(int i=0; i<len; i+=s)
{
int x=1;
for(int j=0; j<t; j++,x=x*w%MOD)
{
int fe=a[i+j],fo=a[i+j+t];
a[i+j]=(fe+x*fo)%MOD;
a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
}
}
}
if(tmp==1) return ;
int inv=qkpow(len,MOD-2);
for(int i=0; i<len; i++)
a[i]=a[i]*inv%MOD;
}
signed main()
{
init(1e5);
pr=1;
m=read();
n=read();
k=read();
for(int i=1; i<=m; i++)
{
int x=read();
pr=pr*inv[x]%MOD;
for(int j=0; j<x; j++)
{
int tmp=C(x,j)*fac[x-1]%MOD*inv[x-j-1]%MOD;
f[i].push_back(tmp);
}
q.push(node{i,x});
}
while(q.size()>1)
{
int x1=q.top().x,s1=q.top().s;
q.pop();
int x2=q.top().x,s2=q.top().s;
q.pop();
len=1;
lg=0;
while(len<=s1+s2) len<<=1,lg++;
for(int i=0; i<len; i++) A[i]=B[i]=0;
for(int i=0; i<s1; i++) A[i]=f[x1][i];
for(int i=0; i<s2; i++) B[i]=f[x2][i];
NTT(A,len,1);
NTT(B,len,1);
for(int i=0; i<len; i++) A[i]=A[i]*B[i]%MOD;
NTT(A,len,-1);
f[x1].clear();
for(int i=0; i<s1+s2; i++)
f[x1].push_back(A[i]);
q.push(node{x1,s1+s2-1});
}
int x=q.top().x,s=q.top().s;
for(int i=k; i<s; i++)
{
int fl=(i-k)%2?-1:1,g=f[x][i]*fac[n-i]%MOD;
ans=((ans+fl*C(i,k)*g)%MOD+MOD)%MOD;
}
ans=ans*pr%MOD;
printf("%lld\n",ans);
}