1. 程式人生 > >【Luogu4921】情侶?給我燒了!(組合計數)

【Luogu4921】情侶?給我燒了!(組合計數)

【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;
}

```