【SSLOJ1452】排行榜

题目

给出 \(n\),求有多少个长度为 \(2n\) 的序列满足以下条件:

  1. 数字 \(1\sim n\) 每个正好各出现两次。
  2. 仅存在一个数字 \(x\) 满足 \(x\) 所在位置前的数字出现次数都没有 \(x\) 多。

思路

首先显然的是,对于一个满足条件 1 的序列,第一个位置的数字必然满足条件 2,所以这个数字 \(x\) 必然就是该序列的第一个数字。
那么假设第一个数字位置为 \(1\)\(i\),那么显然需要满足第 \(2\sim i-1\) 的数字各不相同。而 \(i+1\sim 2n\) 的数字无特殊要求。
那么考虑枚举第一个数字第二次出现的位置 \(i\),然后这种情况下的方案数就是 \(2\sim i-1\) 合法的方案书乘 \(i+1\sim n\) 合法的方案数。
那么前者相对好求,答案显然是从\(n-1\) 个数字中选出 \(i-2\) 个数字的排列。可以写作 \(C^{i-2}_{n-1}!\)
后者没有什么特殊要求,所以可以先看做有 \(n-i\) 个数字随便排列即 \((2n-i)!\),但是由于还有 \(n+1-i\) 个数字可以被选两次,所以每一种等价的情况被计算了 \(2^{n+1-i}\) 次。所以最终有 \(\frac{(2n-i)!}{2^{n+1-i}}\) 种情况。
又因为第一个位置有 \(n\) 种可能,所以答案为

\[n\times \sum^{n+1}_{i=1}C^{i-2}_{n+1}!\times \frac{(2n-i)!}{2^{n+1-i}} \]

但是这题稍微卡常。我们发现 \(2^{n+1-i}\) 其实可以在枚举过程中顺便递推,避免了每次计算花费过多时间。

时间复杂度 \(O(n\log n)\)

\(\operatorname{Update:}\)这道题还有递推式,可以在 \(O(n)\) 复杂度内求出。暂时不会证。

代码

#include 
using namespace std;
typedef long long ll;

const int N=2000010,MOD=998244353;
int n;
ll ans,power,fac[N];

inline ll fpow(ll x,ll k)
{
	ll s=1;
	for (;k;k>>=1,x=x*x%MOD)
		if (k&1) s=s*x%MOD;
	return s;
}

inline ll C(int a,int b)
{
	ll inv=fpow(fac[b]*fac[a-b]%MOD,MOD-2);
	return fac[a]*inv%MOD;
}

int main()
{
	scanf("%d",&n);
	fac[0]=1;
	for (register int i=1;i<=2*n;i++)
		fac[i]=fac[i-1]*i%MOD;
	ll inv2=fpow(2LL,MOD-2);
	power=1LL;
	for (register int i=n+1;i>=2;i--)
	{
		ll s1=fac[i-2]*C(n-1,i-2)%MOD;
		ll s2=fac[2*n-i]*power%MOD;
		ans=(ans+s1*s2)%MOD;
		power=power*inv2%MOD;
	}
	printf("%lld",ans*n%MOD);
	return 0;
}

你可能感兴趣的:(【SSLOJ1452】排行榜)