1. 程式人生 > >[聯合集訓6-18]不同班級 容斥+分治NTT

[聯合集訓6-18]不同班級 容斥+分治NTT

我們設f(x)是至少有x個人與本班人匹配的方案數,那麼根據容斥就有Ans=i=0m(1)if(i)(ni)!
ai=bi=1的時候是經典的錯排問題,f(x)=(nx)
對於一般的情況,我們列舉班級iki個人與本班人匹配,就有:

f(x)=i=1nki=0min(ai,bi)(aik1)(bik1)k1![Σk=x]
發現這就是一個類似揹包的東西,直接分治NTT就能求出所有的f(x)

程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 131100
#define ll long long 
#define up(x,y) x=(x+(y))%mod
using namespace std;
const int mod=998244353;
int n,a[N],b[N],c[N],w[20][N],r[N],ans;
ll fac[N],ifac[N],p[N],q[N];
ll ksm(ll a,ll b)
{
    ll r=1;
    for(b=(b+mod-1
)%(mod-1);b;b>>=1,a=a*a%mod) if(b&1) r=r*a%mod; return r; } ll C(ll a,ll b) { return a<b?0:fac[a]*ifac[b]%mod*ifac[a-b]%mod; } void ntt(ll a[],int m,int dft) { for(int i=0;i<m;i++) r[i]=(r[i>>1]>>1)|((i&1)*(m>>1)); for(int i=0;i<m
;i++) if(i<r[i]) swap(a[i],a[r[i]]); for(int i=1;i<m;i<<=1) { ll wn=ksm(3,(mod-1)/(i<<1)*dft); for(int j=0;j<m;j+=(i<<1)) { ll wk=1; for(int k=j;k<j+i;k++) { ll x=a[k],y=a[k+i]*wk%mod; a[k]=(x+y)%mod; a[k+i]=(x-y+mod)%mod; wk=wk*wn%mod; } } } if(dft==-1) for(int i=0,inv=ksm(m,mod-2);i<m;i++) a[i]=a[i]*inv%mod; } void solve(int d,int l,int r,int L,int R) { if(l==r) { for(int i=L+1;i<=R;i++) w[d][i]=C(a[l],i-L)*C(b[l],i-L)%mod*fac[i-L]%mod; return ; } int mid=(l+r)>>1,m=1; while(m<=R-L) m<<=1; solve(d+1,l,mid,L,c[mid]); solve(d+1,mid+1,r,c[mid],R); for(int i=1;i<m;i++) p[i]=q[i]=0; p[0]=q[0]=1; for(int i=L+1;i<=c[mid];i++) p[i-L]=w[d+1][i]; for(int i=c[mid]+1;i<=R;i++) q[i-c[mid]]=w[d+1][i]; ntt(p,m,1);ntt(q,m,1); for(int i=0;i<m;i++) p[i]=p[i]*q[i]%mod; ntt(p,m,-1); for(int i=L+1;i<=R;i++) w[d][i]=p[i-L]; } int main() { scanf("%d",&n); int tot=0; for(int i=1;i<=n;i++) { scanf("%d%d",&a[i],&b[i]); c[i]=c[i-1]+min(a[i],b[i]); tot+=a[i]; } fac[0]=1; for(int i=1;i<=100000;i++) fac[i]=fac[i-1]*i%mod; ifac[100000]=ksm(fac[100000],mod-2); for(int i=99999;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod; solve(0,1,n,0,c[n]); w[0][0]=1; for(int i=0;i<=c[n];i++) up(ans,w[0][i]*fac[tot-i]%mod*((i&1)?mod-1:1)); printf("%lld",ans); return 0; }