牛客5633D - 宝石装箱

题意

  • nn 个物品装进 nn 个箱子,每个箱子恰好装一个物品。
  • 要求第 ii 个物品不能装入第 aia_i 个箱子中。
  • 问有多少种合法的方案。

思路

错误思路

  • 线性容斥。计算不合法的总方案数。ans=n!ans=n!-不合法的总方案数
  • =i=1nii不合法的总方案数=\sum_{i=1}^{n}前i个箱子合法且第i个箱子不合法的方案数
  • 上面这个表达式是正确的,但是 前i个箱子合法且第i个箱子不合法的方案数 太难算。

正确思路

  • 线性容斥。
  • 我们发现 至少有 ii 个箱子不合法 好算
  • 我们从至少有 ii 个箱子不合法入手
  • dp[i]dp[i] 表示至少有 ii 个箱子不合法
  • ans=i=1n[(1)i×dp[i]×(ni)!]ans=\sum_{i=1}^{n}[(-1)^i\times dp[i]\times(n-i)!]
  • 如何计算 dp[i]dp[i]
  • 背包转移。

代码

#include <cstdio>
#include <iostream>
#define int long long
const int N		= 1e6+10;
const int MOD	= 998244353;
using namespace std;

int bin[N];
int cnti[N],dp[N];
int n;

void Solve()
{
	bin[0] = 1;
	for (int i=1; i<=n; i++)
	{
		bin[i] = bin[i-1]*i%MOD;
	}
	
	dp[0] = 1;
	for (int i=1; i<=n; i++)
	{
		if(!cnti[i])continue;
		
		for (int j=n; j>=1; j--)
		{
			dp[j] += dp[j-1]*cnti[i]%MOD, dp[j]%=MOD;
		}
	}
	
	int ans = 0;
	int base = -1;
	for (int i=0; i<=n; i++)
	{
		base *= -1;
		ans += (base * dp[i] * bin[n-i] + MOD) % MOD, ans+=MOD,ans%=MOD;
	}
	
	printf("%lld\n",ans);
}

signed main()
{
	scanf("%lld",&n);
	for (int i=1; i<=n; i++)
	{
		int x;
		scanf("%lld",&x);
		cnti[x]++;
	}
	Solve();
	
	return 0;
}