【容斥原理】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)!\)
同理可知:
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_,)ノ
*/