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