【容斥原理】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_,)ノ
*/