【洛谷6672】[清华集训2016] 你的生命已如风中残烛(Raney引理)

点此看题面

  • 共有(m)张牌,其中(n)张牌上写有数字(w_i),满足(sum w_i=m)
  • 一开始你先摸走牌库最顶上的一张牌,每当你摸到一张写着数字(x)的牌,你就再摸(x)张牌。
  • 问有多少种牌库,你能把所有牌摸完。
  • (nle40,w_ile10^5)

(Raney)引理

做这道题必需的一个引理:

一个环(x_1,x_2,...,x_n)满足(sum x_i=1),在将它裂开形成的(n)条可能的链中,恰有一条满足所有前缀和都是正数。

懒得另开一篇博客,就在这里顺便讲一下用(Raney)引理证明卡特兰数(C_n=frac{(2n)!}{n!(n+1)!})

因为共有(n)(1)(n)(-1),它们的和不为(1),因此我们强行在序列开头加上一个(1),这样就满足所有数和为(1)

由于所有前缀和为正数,因此第一个数肯定是(1)而不是(-1),那么去掉开头就变成了剩余所有前缀和非负,恰好满足卡特兰数的限制条件。

这样就能推导出卡特兰数的通项公式(C_n=frac{(2n)!}{n!(n+1)!})

关于此题的解法

首先我们把所有数都减去(1),此时所有数和为(0),且需要满足所有前缀都是非负数。

一个(naive)的想法是和前面卡特兰数的推导一样,强行在序列开头加上一个(1),满足所有数和为(1),且变成所有前缀都是正数。

但是,毕竟还是不一样,因为卡特兰数中正数只有(1),开头必须是(1),而这道题则不一定,所以会计算出错。

于是我们必须换个思路,发现有一个东西是唯一的,就是负数只有(-1)

所以我们改成在序列末尾加上一个(-1),那么序列总和是(-1),这样一来只要将所有数都取反并逆序,就转化成了(Raney)引理的标准模型,且可以保证计算不重复(因为取反后的正数就只有(1)了)。

此时共有(m+1)个数,因此圆排列数量应该是((m+1-1)!=m!)

然后为了除掉这个(-1)的影响,考虑原本有(m-n)(-1),现在添上一个(-1)就有(m-n+1)(-1),为了消除贡献还需要将(m!)除去(m-n+1)

因此答案就是:

[frac{m!}{m-n+1} ]

因为(m-n+1le m),所以只要在求阶乘的时候忽略其影响即可。

代码:(O(sum w_i))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 40
#define X 998244353
using namespace std;
int n,m;
int main()
{
	RI i,t;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&t),m+=t;//m为所有数的总和
	for(t=i=1;i<=m;++i) i^(m-n+1)&&(t=1LL*t*i%X);return printf("%d
",t),0;//计算答案,忽略m-n+1
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6672.html