• [2018.7.4集训]magic-多项式相关-容斥原理


    题目大意

    有$m$种卡片,共$n$张,第$i$种数量为$a_i$张。
    将$n$张卡顺次摆放可以得到一个魔术序列,在魔术序列中,若相邻两张卡种类相同,则它们被称为一个魔术对。
    求出本质不同的恰好包含$k$个魔术对的魔术序列数量,对$998244353$取模。

    $0 leq k,n leq 100000 , 1leq m leq 20000,sum_{i=1}^m a_i=n$

    题解

    本质不同很难做,那么先假装同种的卡片互不相同。

    考虑容斥,设$g_i$表示至少存在$i$个魔术对的序列数。
    为了计算这个$g_i$,设$f[i][j]$表示前$i$种卡片至少钦定了$j$个魔术对的方案数。

    可以发现,最终每种卡片的方案将会是一个个连续段,且每种方案贡献的对数为$a_i$减去分段数的值。
    于是,对于每次转移,钦定当前种的若干张强行接在其他没有被钦定的魔术卡后方来组成一个个段。
    这样,剩下的没有被钦定的有可能会师一段序列的开头,随意往原序列里插即可~

    于是设当前种选择了$l$张,那么有转移:

    $$f[i][j]=f[i-1][j-l]*inom{a_i}{l}*frac{(a_i-1)!}{(a_i-l-1)!}$$

    由于在dp过程中,咱们相当于只考虑没被钦定的卡片的顺序,再随意插入被钦定的卡,因此现在需要考虑被钦定了的卡的贡献,于是除去一个阶乘,有$g_i=frac{f[m][i]}{(n-i)!}$。

    可以发现,$f[i][j]$的转移是多项式的形式,可以利用堆实现的分治$FFT$优化至$O(nlog^2n)$。

    接下来,就需要容斥了。
    设$f_i$为恰好有$i$个魔术对的方案数量,那么有一个明显的式子:
    $$g_i=sum_{j=i}^{n}{j choose i}f_j$$
    于是考虑广义容斥原理:
    $$f_i=sum_{j=i}{n}(-1){j-i}{j choose i}g_j$$

    由于最初假设了同种卡片互不相同,那么除去每种卡片自身的排列,有

    $$ans=frac{f_k}{prod_{i=1}^{m}a_i!}$$

    于是收工!

    代码:

    #include<queue>
    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    inline int read()
    {
    	int x=0;char ch=getchar();
    	while(ch<'0' || '9'<ch)ch=getchar();
    	while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    	return x;
    }
    
    typedef long long ll;
    typedef pair<int,int> pr;
    const int M=20009;
    const int N=400009;
    const ll md=998244353;
    
    int m,n,k,a[M];
    ll fac[N],inv[N],ans;
    ll f[N],g[N];
    priority_queue<pr,vector<pr>,greater<pr> > q;
    vector<int> h[M];
    
    inline ll qpow(ll a,ll b)
    {
    	ll ret=1;
    	while(b)
    	{
    		if(b&1)ret=ret*a%md;
    		a=a*a%md;b>>=1;
    	}
    	return ret;
    }
    
    inline void init()
    {
    	fac[0]=1;
    	for(int i=1;i<N;i++)
    		fac[i]=fac[i-1]*i%md;
    	inv[N-1]=qpow(fac[N-1],md-2);
    	for(int i=N-1;i>=1;i--)
    		inv[i-1]=inv[i]*i%md;
    }
    
    inline ll c(ll a,ll b)
    {
    	if(a<b)return 0;
    	return fac[a]*inv[b]%md*inv[a-b]%md;
    }
    
    namespace ntt
    {
    	int rev[N];
    
    	inline void ntt(ll *a,int n,int f)
    	{
    		for(int i=0;i<n;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
    		for(int h=2;h<=n;h<<=1)
    		{
    			ll w=qpow(3,(md-1)/h);
    			if(f)w=qpow(w,md-2);
    			for(int j=0;j<n;j+=h)
    			{
    				ll wn=1ll,x,y;
    				for(int k=j;k<j+(h>>1);k++)
    				{
    					x=a[k];y=a[k+(h>>1)]*wn%md;wn=wn*w%md;
    					a[k]=(x+y)%md;a[k+(h>>1)]=(x-y+md)%md;
    				}
    			}
    		}
    		if(f)
    			for(ll i=0,invn=qpow(n,md-2);i<n;i++)
    				a[i]=a[i]*invn%md;
    	}
    
    	inline void initrev(int n)
    	{
    		for(int i=0;i<n;i++)
    			rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
    	}
    
    	inline void mul(vector<int> &a,int n,vector<int> &b,int m,vector<int> &c)
    	{
    		static ll d[N],e[N],l;
    		for(l=1;l<=n+m;l<<=1);initrev(l);
    		for(int i=0;i<n;i++)d[i]=a[i];
    		for(int i=n;i<l;i++)d[i]=0;
    		for(int i=0;i<m;i++)e[i]=b[i];
    		for(int i=m;i<l;i++)e[i]=0;
    		ntt(d,l,0);ntt(e,l,0);
    		for(int i=0;i<l;i++)
    			d[i]=d[i]*e[i]%md;
    		ntt(d,l,1);c.resize(n+m-1);
    		for(int i=0;i<n+m-1;i++)
    			c[i]=d[i];
    	}
    }
    
    int main()
    {
    	init();
    	m=read();n=read();k=read();
    	for(int i=1;i<=m;i++)
    		a[i]=read();
    
    	for(int i=1;i<=m;i++)
    	{
    		for(int j=0;j<a[i];j++)
    			h[i].push_back(c(a[i],j)*fac[a[i]-1]%md*inv[a[i]-j-1]%md);
    		q.push(pr(h[i].size(),i));
    	}
    
    	for(int t=1;t<=m-1;t++)
    	{
    		int px=q.top().second;q.pop();
    		int py=q.top().second;q.pop();
    		ntt::mul(h[px],h[px].size(),h[py],h[py].size(),h[px]);
    		q.push(pr(h[px].size(),px));
    	}
    	
    	int lst=q.top().second;q.pop();
    	for(int i=0;i<h[lst].size() && i<=n;i++)
    		g[i]=h[lst][i]*fac[n-i]%md;
    
    	ll ans=0;
    	for(int j=k;j<=n;j++)
    		(ans+=(((j-k)&1)?(md-1ll):1ll)*c(j,k)%md*g[j]%md)%=md;
    	for(int i=1;i<=m;i++)
    		ans=ans*inv[a[i]]%md;
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    ZT 安卓手机的安全性 prepare for Q
    ZT pthread_cleanup_push()/pthread_cleanup_pop()的详解
    <Lord don’t move that mountain>
    C++浅拷贝和深拷贝的区别
    001 Python简介 输入输出
    016 可等待计时器对象.6
    016 EventDemo 5
    016 事件内核对象4
    016 内核对象的Signal状态3
    016 句柄2
  • 原文地址:https://www.cnblogs.com/zltttt/p/9271521.html
Copyright © 2020-2023  润新知