1. 程式人生 > 其它 >【題解】 P4921 【情侶?給我燒了!】

【題解】 P4921 【情侶?給我燒了!】

【題解】 P4921 【情侶?給我燒了!】

這題加強版能有黑我把電腦吃了

寫一篇題解來紀念一下我的首黑(?)。

記得在首藍和首紫的時候我也確實是寫了一篇題解作為紀念。

其實我也不知道我咋 A 掉這玩意的。。我就把它原題的程式碼複製貼上過來,改了個數據範圍和輸入,然後就 A 了它的加強版(?)。

說實話,真不覺得這題有黑,感覺頂多藍吧。。不就是個高中數學排列組合……(雖然我自己在錯排那塊卡了挺久的)

題目連結

P4921 [MtOI2018]情侶?給我燒了!

加強版

思路分析

這裡就寫原題的分析了,加強版和原題實際上差不多。

參考資料:題解 P4921 【情侶?給我燒了!】 - Shone 的部落格 - 洛谷部落格

看到這道題目,我們可以把問題拆成兩部分來做。

我們可以知道,電影院中的人實際上是有兩部分構成的:一是”和睦“的情侶,二是其它“無所謂“的人。(雖然這樣描述可能有點不太恰當,但是全當理解題意就這麼說吧。)

對於“和睦”情侶:首先從 \(n\) 對情侶中選出 \(k\) 對作為“和睦”的情侶,即 \(\mathrm C_n^k\)。(千萬別忘了這個,我當時就是忘了這個)。然後對於 \(k\) 對情侶,我們要將他們安放在 \(k\) 排座位上,所以我們要從 \(n\) 排座位中選出 \(k\) 對,也就是 \(\mathrm A_n^k\)。接下來還沒完,我們還要確定,對於每一對情侶要坐的這一排座位,哪個坐在”左邊”哪個坐在”右邊“(感性理解一下),對於每一排座位,每對情侶有兩種坐法,那麼總共 \(k\)

對情侶,有 \(2^k\) 種做法。根據乘法原理,對於 \(k\) 對和睦情侶坐的位置的總方案數就為 \(\mathrm C_n^k \times \mathrm A_n^k \times 2^k\)

對於其它“無所謂”的人:首先,其它無所謂的人肯定是不能隨便坐的,因為我們要求恰好 \(k\) 對情侶,而如果其它人隨便坐可能會產生新的“和睦”的情侶,這就不符合恰好 \(k\) 對情侶的要求了。所以我們要把這些剩下的人安排到剩下的 \(2n-2k\) 個座位上,讓他們滿足:不能存在任何一對“和睦”的情侶。那麼實際上,我們要求的就是剩下的 \(n-k\) 對情侶都錯開的方案數。

那麼我們定義 \(g(x)\)

為讓 \(x\) 對情侶都錯開的方案數。

我們從第一排開始考慮:

一共有三種情況:兩男兩女或者一男一女(不“和睦”);

  • 兩男:首先選出兩男的方案數是 \(x(x-1)\),那麼我們還必須考慮原本和他們應該“和睦”的配偶在他們被選過之後的情況:

    1. 如果他們的配偶坐在一排,那麼要在剩下的 \(x-1\) 排中選擇一排匹配給他們,那麼方案數:\(2(x-1) \times g(x-2)\)

    2. 如果他們不坐在一塊,就強制把他們看做一對情侶來保證以後不坐在一塊,方案數:\(g(x-1)\)

  • 兩女:和兩男的情況相同。

  • 一男一女:列舉一男一女,可以交換順序的方案數為 \(x(x−1)\)

    所以,\(g(x)=4x(x-1) \times [g(x-1)+2(x-1)\times g(x-2)]\)

    注意:這裡要分清楚加法原理和乘法原理,不要把符號給搞錯了。

    時間複雜度:預處理 1-2000(加強版是 1-5e6) 的階乘逆元g(x)。那麼單次詢問時間複雜度 \(O(n)\),總時間複雜度 \(O(Tn)\)

易錯點

  1. 那麼多數相乘肯定會爆 int,所以請開 long long。另外防止爆 long long,請每次乘完都模一遍 \(mod\)

  2. 一定要在想之前把這道題的邏輯搞清楚,很容易亂(比如我在剛開始就漏了第一個 \(\mathrm C_n^k\),然後弄清楚乘法原理和加法原理。

程式碼實現

//luoguP4921
#include<iostream>
#include<cstdio>
#define int long long//記得開 long long
using namespace std;
const int maxn=5e6+10;
const int mod=998244353;
int jc[maxn],inv[maxn],in[maxn],jcc[maxn];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void init()
{
	jc[0]=jc[1]=inv[0]=inv[1]=in[0]=in[1]=1;//預處理,一定要注意把 inv[0] 設成 1。
	for(int i=2;i<=2000;i++)//預處理 i 的階乘,階乘逆元
	{
		jc[i]=jc[i-1]*i%mod;
		inv[i]=(mod-mod/i)%mod*inv[mod%i]%mod;
		in[i]=in[i-1]*inv[i]%mod;
	}
	jcc[0]=1;jcc[1]=0;//預處理 g(x)
	for(int i=2;i<=1000;i++)jcc[i]=4*(i-1)*i%mod*(jcc[i-1]+2*(i-1)*jcc[i-2]%mod)%mod;
}

int A(int n,int m){return jc[n]*in[n-m]%mod;}

int C(int n,int m){return jc[n]*in[n-m]%mod*in[m]%mod;} //記得要多 % 幾次。

int qpow(int a,int T)
{
	int ret=1;
	while(T)
	{
		if(T&1) (ret*=a)%=mod;
		(a*=a)%=mod;T>>=1;
	}
	return ret;
}

signed main()
{
	int T;
	T=read();
	init();
	//for(int i=1;i<=2000;i++)cout<<jc[i]<<" "<<inv[i]<<" "<<in[i]<<endl;
	while(T--)
	{
		int n;
		n=read();
		for(int i=0;i<=n;i++)
		{
			cout<<C(n,i)*qpow(2,i)%mod*A(n,i)%mod*jcc[n-i]%mod<<endl;
		}
	}
	return 0;
}