(nowcoder22529C)dinner(容斥原理+排列组合)

题目链接:登录—专业IT笔试面试备考平台_牛客网

题目:

样例输入:

1000000

样例输出:

270258012

分析:设Ai表示第i对cp相邻这个性质,那么我们就是要求N((1-A1)(1-A2)……(1-An)),先表示出i对cp相邻的数目,由于n对cp是没有区分的,所以我们可以选择任意i对cp表示其相邻,选取方案数是C(n,i),每对cp相邻我们就把他俩看成一个点,那么总共就是n*2-i个点,由于是排成环,那么我们需要以任意一个点作为头节点,其余点进行全排列即可,那么排列方案数就是(n-2*i-1)!,每对cp内部排列是任意的,所以就是两种情况,总共有i对cp,所以贡献就是2^i,再套上容斥原理公式即得:ans=\sum_{i=0}^{n}((-1)^i*C_{n}^{i}*2^i*(2*n-i-1)!)

这道题对时间和空间的要求都比较严格,所以必须要尽可能地优化,我下面附上未优化代码和优化后的代码供大家比对

未优化的代码(超时)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
const int N=6e7+10,mod=1e9+7;
ll fac[N],inv[N];
ll qpow(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
ll C(ll a,ll b)
{
	return fac[a]*inv[b]%mod*inv[a-b]%mod;
}
int main()
{
	ll n;
	cin>>n;
	if(n==1)
	{
		puts("0");
		return 0;
	}
	fac[0]=inv[0]=1;
	for(int i=1;i<=2*n;i++)
		fac[i]=fac[i-1]*i%mod;
	inv[2*n]=qpow(fac[2*n],mod-2);
	for(int i=2*n-1;i>=1;i--)
		inv[i]=inv[i+1]*(i+1)%mod;
	ll ans=0;
	int sign=1;
	for(int i=0;i<=n;i++)
	{
		ans=(ans+sign*C(n,i)*fac[2*n-i-1]%mod*qpow(2,i))%mod;
		ans=(ans+mod)%mod;
		sign=-sign;
	}
	printf("%lld",ans);
	return 0;
}

优化后代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
const int N=3e7+10,mod=1e9+7;
int inv[N];
ll qpow(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
int main()
{
	ll n;
	cin>>n;
	if(n==1)
	{
		puts("0");
		return 0;
	}
	inv[0]=1;
	for(int i=1;i<=n;i++)
		inv[i]=1ll*inv[i-1]*i%mod;
	inv[n]=qpow(inv[n],mod-2);
	for(int i=n-1;i>=1;i--)
		inv[i]=1ll*inv[i+1]*(i+1)%mod;
	ll ans=0;
	ll sign;
	ll fac1=1,fac2=1,p2=1;
	ll inv2=qpow(2,mod-2);//inv2记录2关于mod的逆元 
	//fac1记录fac[n],fact2记录fac[2*n-i-1],p2记录qpow(2,i) 
	for(int i=1;i<=n;i++) fac1=fac1*i%mod,p2=p2*2%mod;
	for(int i=1;i<n;i++) fac2=fac2*i%mod;
	if(n&1) sign=-1;
	else sign=1;
	for(int i=n;i>=0;i--)
	{
//		ans=(ans+sign*C(n,i)*fac[2*n-i-1]%mod*qpow(2,i))%mod;
		ans=(ans+sign*fac1*inv[i]%mod*inv[n-i]%mod*fac2%mod*p2)%mod;
		fac2=fac2*(2*n-i)%mod;
		p2=p2*inv2%mod; 
		sign=-sign;
	}
	ans=(ans%mod+mod)%mod;
	printf("%lld",ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值