1. 程式人生 > 其它 >【容斥原理】NC19857-最後的晚餐

【容斥原理】NC19857-最後的晚餐

【容斥原理】NC19857-最後的晚餐

題目連結:https://ac.nowcoder.com/acm/problem/19857

題目描述

​ **YZ(已被和諧)的食堂實在是太擠辣!所以Apojacsleam現在想邀請他的一些好友去校外吃一頓飯,並在某酒店包下了一桌飯。

​ 當Apojacsleam和他的同學們來到酒店之後,他才發現了這些同學們其實是N對cp,由於要保護廣大單身狗的弱小心靈(FF!),所以他不想讓任意一對情侶相鄰。

​ 說明:

​ ·酒店的桌子是恰好有2N個位置的圓桌。

​ ·客人恰好是N對cp,也就是說,圓桌上沒有空位。

​ ·桌子的每一個位置是一樣的,也就是說,如果兩種方案可以通過旋轉得到,那麼這就可以視為相等的。

​ 現在,你需要求出,將任意一對情侶不相鄰的方案數。

輸入描述:

一行一個正整數N,表示cp的對數。

輸出描述:

一行一個非負整數,表示答案對1000000007取模後的值。

示例1

輸入

2

輸出

2

說明

兩種方案:

假設1-2、3-4是兩對情侶。

方案有1-3-2-4

1-4-2-3

或者你也可以認為1-3-2-4

2-3-1-4

是合法的方案。

示例2

輸入

25

輸出

535659175

示例3

輸入

1000000

輸出

270258012

說明

對於20%的資料,1<=N<=5
對於30%的資料,1<=N<=20
對於50%的資料,1<=N<=100
對於70%的資料,1<=N<=200000
對於100%的資料,1<=N<=30000000

題意

給定n對情侶和2n個首位相連的位置,問每對情侶不相鄰的坐法

思路

容斥,所有排列情況減去不符合的情況(經過一系列處理)

隊友的想法:插空法,在每對符合題意的情侶中間插入別的情侶,一層層迭代,但是這樣會漏掉本來不符合但插空後符合的情況,所以就歪了

想法與過程

首先解決第一個問題,圍一圈所有人的種數是多少,設一圈共有\(n\)個人,如果排列不是首尾相連,則共有\(n!\)種排列方式,而首尾相連以\(x(x∈[1,n])\)(共n個) 開頭的每種排法都可以通過旋轉得到後面某一個排列,故\(n\)個人圍成一排的不同排列共有\((n - 1)!\)種。

然後,隨機分配起點,0對相鄰,則剩下的方案總數為\((2 * n - 1)!\)

= \(2 ^ 0 * C(n,0) * (2 * n - 1)!\)

同理可知:

1對相鄰,\(2 ^ 1 * C(n,1) * (2 * n - 2)!\)

2對相鄰,\(2 ^ 2 * C(n,2) * (2 * n - 3)!\)

....

易得(真的是易得不是直接出來的)

\(ans = \sum_{i=0}^n (-1)^i2^i{n\choose i}(2n-i-1)!\) (翻譯:設i為容斥係數,答案 = 0對相鄰 - 1對相鄰 + 2個相鄰 - 3個相鄰 .....)

其次,能預處理的資料先預處理。

最後的處理,資料範圍\(3e7\)(對我來說)即使\(On\)也很難處理時間和空間,各種方法試過之後無奈使用快讀+int。

(提交了40次勉強卡時間過的程式碼,還是很不完整,上一次提交40次還是在入門時遇到的毒瘤題,註釋就不去掉了,記錄了我wa和tle很多次的心路歷程QAQ)

AC程式碼:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
//#define int long long //這句習慣去掉了 開long long容易超時
#define ull unsigned long long
#define PII pair<int,int>
using namespace std;
typedef long long ll;
const int N = 3e7 + 1;//不加1 可能wa 加多了直接tle
const int mod = 1e9 + 7;
const double pi = acos(-1.0);
int t, n, k;
int l, r;
int fact[N];//階乘預處理
int invfact[N];//階乘的逆元預處理,方便組合數求解
///先說個前提:(1ll)是必須乘的,可能在取模之前有爆int的可能,這個地方也wa過了
int read() {//快讀
	int f = 1, x = 0; char ch;
	do { ch = getchar(); if (ch == '-') f = -1; } while (ch<'0' || ch>'9');
	do { x = x * 10 + ch - '0'; ch = getchar(); } while (ch >= '0'&&ch <= '9');
	return x * f;
}
int quickpow(int a, int b) {//快速冪
	int res = 1;
	while (b > 0) {
		if (b & 1) res = 1ll * res * a % mod;
		b >>= 1;
		a = 1ll * a * a % mod;
	}
	return res;
}
int C(int n, int m) {//組合數處理,沒有用上,最後直接寫的表示式,用上這個函式就超時了
	return 1ll * fact[n] * 1ll * invfact[m] % mod * invfact[n - m] % mod;
}
void solve() {
	if (n == 0 || n == 1) {//特判
		printf("0\n");
		return;
	}
    //階乘,階乘逆元預處理
    //階乘這裡本想用預處理函式init()那樣,結果套起來還是會超時,我不李姐
	fact[0] = 1;
	for (int i = 1; i <= N; i++) {
		fact[i] = 1ll * fact[i - 1] * i % mod;
	}
    
	int m = 3e7;
	invfact[m] = quickpow(fact[m], mod - 2);
	for (int i = m - 1; i >= 0; i--)
	{
		invfact[i] = 1ll * invfact[i + 1] * (i + 1) % mod;
	}
    //上述推出來的表示式: $ans = \sum_{i=0}^n (-1)^i2^i{n\choose i}(2n-i-1)!$ 
	int ans = 0;
	int cc =  fact[n - 1];//表示式中的C(n,m)
	int p = quickpow(2, n);//表示式中的2的冪次
	for (int i = n; i >= 0; i--)
	{
		if (i & 1) ans = (1ll * ans - 1ll * p * (1ll * fact[n] * 1ll * invfact[i] % mod * invfact[n - i] % mod) % mod * 1ll * cc % mod + mod) % mod;
        
		else ans = (1ll * ans + 1ll * p * (1ll * fact[n] * 1ll * invfact[i] % mod * invfact[n - i] % mod) % mod * 1ll * cc % mod + mod) % mod;
        //容斥係數按照奇數偶數集合交集分開處理
		cc = 1ll * cc * 1ll * (2 * n - i) % mod;
		p = 1ll * p * invfact[2] % mod;
	}
	printf("%d\n", ((ans % mod + mod) % mod));//答案取模,輸出
	//cout << ((ans % mod + mod) % mod) << endl;;
	return;
}
int main() {
	//ios::sync_with_stdio(false);
	//cin.tie(0); cout.tie(0);
	//while (cin >> n) {
			n = read();
			solve();
	//}
	return 0;
}
/*
	i raised a cute kitty in my code,
	my friend who pass by can touch softly on her head:)
         /l、 
   Meow~(゚、 。7
         |、 ~ヽ
         じしf_,)ノ
*/