【容斥、二项式反演】【洛谷P4831】【xjoi2021省选训练22】Scarlet loves WenHuaKe

题目

https://www.luogu.com.cn/problem/P4831

思路

不会有人用多项式吧(

题目转换:将 1.. m 1..m 1..m 2 2 2 个(表示纵坐标),填入 n n n 对无序二元组(表示行),且二元组内两数不同

f n , m f_{n,m} fn,m 为答案, g n , m g_{n,m} gn,m 1.. m 1..m 1..m 各有 2 2 2 个,放进 2 n 2n 2n 个格子的方案数。

先思考 g的计算:考虑选用了 n − i n-i ni 对一样的数字,然后剩下的位置要从 m − n − i m-n-i mni 种数字中选出 2 i 2i 2i 个单独的数字补足 2 n 2n 2n 个格子,之后就是有部分元素相同的全排列:

g n , m = ( 2 n ) ! ∑ i = 0 m i n ( n , m − n ) C m n − i C m − n + i 2 i 2 n − i g_{n,m}=(2n)!\sum_{i=0}^{min(n,m-n)}\frac{C_m^{n-i}C_{m-n+i}^{2i}}{2^{n-i}} gn,m=(2n)!i=0min(n,mn)2niCmniCmn+i2i

考虑 f f f g g g 的关系: g g g中“ 2 n 2n 2n 个格子”等价于“ n n n 对有序二元组”,那么枚举有 n n n 对有序二元组中选 i i i 对填写了相同的数字,再用 m m m 中的 i i i 种数字排进他们,剩下的位置是 f n − i , m − i f_{n-i,m-i} fni,mi 的情况:
g n , m = ∑ i = 0 n C n i A m i 2 n f n − i , m − i g_{n,m}=\sum_{i=0}^{n}C_n^iA_m^i2^nf_{n-i,m-i} gn,m=i=0nCniAmi2nfni,mi

二项式反演一波:

f n , m = 1 2 n ∑ i = 0 n ( − 1 ) i C n i A m i g n − i , m − i f_{n,m}=\frac{1}{2^n}\sum_{i=0}^{n}(-1)^iC_n^iA_m^ig_{n-i,m-i} fn,m=2n1i=0n(1)iCniAmigni,mi

时间复杂度 O ( n ∗ m i n ( n , m − n ) ) O(n*min(n,m-n)) O(nmin(n,mn))

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+77,mod=998244353;
int n,m;
ll f[2077][2077],ans,fac[N],unfac[N];
ll power(ll x,ll t)
{
    ll b=1;
    while(t)
    {
        if(t&1) b=b*x%mod; x=x*x%mod; t>>=1;
    }
    return b;
}
ll c(ll x)
{
    return x*(x-1)/2;
}
void init(int n)
{
    fac[0]=1;
    for(int i=1; i<=n; i++) fac[i]=fac[i-1]*i%mod;
    unfac[n]=power(fac[n],mod-2);
    for(int i=n-1; i>=0; i--) unfac[i]=unfac[i+1]*(i+1)%mod;
}
ll C(ll x,ll y)
{
    if(y>x) return 0;
    return fac[x]*unfac[y]%mod*unfac[x-y]%mod;
}
ll A(ll x,ll y)
{
    if(y>x) return 0;
    return fac[x]*unfac[x-y]%mod;
}
ll G(ll n,ll m)
{
    ll yjy=0;
    for(int i=0; i<=min(n,m-n); i++)
    {
        (yjy+=C(m,n-i)*C(m-n+i,2*i)%mod*power(power(2,n-i),mod-2)%mod)%=mod;
    }
    return yjy*fac[2*n]%mod;
}
int main()
{
    scanf("%d%d",&n,&m);
/*  if(n<=2000&&m<=2000)
    {
        if(n==m&&m==0)
        {
            printf("1"); return 0;
        }
        f[1][0]=c(m);
        for(int i=1; i<n; i++) for(int j=max(2*i-m,0); j<=i; j++) if(f[i][j])
        {
            (f[i+1][j+2]+=f[i][j]*c(2*i-2*j)%mod)%=mod;
            (f[i+1][j+1]+=f[i][j]*(2*i-2*j)%mod*(m-2*i+j)%mod)%=mod;
            (f[i+1][j]+=f[i][j]*c(m-2*i+j)%mod)%=mod;
        }
        for(int i=0; i<=n; i++) (ans+=f[n][i])%=mod;
        printf("%lld",ans);
    }
    else*/
    {
        ll _1=-1,ans=0;
        init(m*2);
        for(int i=0; i<=n; i++)
        {
            _1=-_1;
            (ans+=(_1*C(n,i)%mod*A(m,i)%mod*G(n-i,m-i)%mod+mod)%mod)%=mod;
        }
        printf("%lld",ans*power(power(2,n),mod-2)%mod);
    }
     
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值