【Luogu4921】情侣?给我烧了!(组合计数)

本文探讨了一道有趣的组合计数题目,通过巧妙的方法解决情侣坐排问题,避免平方复杂度,采用预处理方法实现O(n)单次复杂度。

【Luogu4921】情侣?给我烧了!(组合计数)

题面

洛谷

题解

很有意思的一道题目。
直接容斥?怎么样都要一个平方复杂度了。
既然是恰好\(k\)对,那么我们直接来做:
首先枚举\(k\)对人出来\(\displaystyle {n\choose k}\),然后枚\(k\)排座位出来\(\displaystyle {n\choose k}\),这些人间的顺序关系\(k!\),然后这些人可以左右交换\(2^{k}\)
好的,现在的问题转化为了剩下\(n-k\)对人,两两之间不能坐在一排,求方案数。
首先这\(n-k\)对人的顺序提前算好\((n-k)!\),然后左右顺序忽视掉\(2^{n-k}\)
假装\(n\)对人完全错开的方案数是\(f(n)\)
类似错排问题,然而并不是错排问题。类似错排问题的递推公式的想法,每次加入最新的一组。
那么当前这一组随便和前面哪一排找个人互换就好了,一共有两种交换方法。所以这一部分的贡献是\((n-1)*2*2*2*f(n-1)\)
还有特殊情况就是原本换的那组两个人在一排,现在和这一排强制交换,有两种交换方法。那么这部分的贡献就是\((n-1)*2*f(n-2)\)
那么转移凑合一下就是\(f(n)=2(n-1)(f(n-1)+f(n-2))\)
再把答案式写一下:
\[Ans_k=2^n{n\choose k}^2k!(n-k)!f(n-k)\]
这样子预处理\(f\)之后单次的复杂度为\(O(n)\)

不过我还看到了一种很有意思的方法。
\(f[i][j]\)表示\(i\)对情侣中恰好有\(j\)对坐在一起的方案数,\(g[i]\)表示\(i\)对情侣都不坐在一起的方案数。
那么\(\displaystyle f[n][k]={n\choose k} A_n^k2^k*g[n-k]\)
那么反过来\(\displaystyle g[n]=(2n)!-\sum_{i=1}^n f[n][i]\)
这样子是\(O(n^2)\)的,感觉很有意思的方法。

代码是前面那种方法

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 1010
#define MOD 998244353
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,f[MAX],jc[MAX],jv[MAX],inv[MAX],bin[MAX];
int C(int n,int m){return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
int main()
{
    int T=read();jc[0]=jv[0]=inv[0]=inv[1]=f[0]=bin[0]=1;
    for(int i=1;i<MAX;++i)f[i]=2ll*(i-1)*(f[i-1]+f[i-2])%MOD;
    for(int i=2;i<MAX;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    for(int i=1;i<MAX;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    for(int i=1;i<MAX;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    for(int i=1;i<MAX;++i)bin[i]=2ll*bin[i-1]%MOD;
    while(T--)
    {
        n=read();
        for(int i=0;i<=n;++i)
            printf("%lld\n",1ll*bin[n]*C(n,i)%MOD*C(n,i)%MOD*jc[n-i]%MOD*jc[i]%MOD*f[n-i]%MOD);
    }
    return 0;
}

```

转载于:https://www.cnblogs.com/cjyyb/p/10176689.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值