【LOJ】小 Q 的序列

题目描述

(quad) 传送门

题解


(quad mathcal Solution mathcal A:)

(quad) 毛夫的数学做法


(quad mathcal Solution mathcal B:)

(quad) 我们令:

[b_i = a_i+i ]

(quad) 我们考虑 (b) 的一个子集:

[b_{t_1},b_{t_2},cdotcdotcdot,b_{t_k} o a_{t_1},a_{t_2},cdotcdotcdot,a_{t_k} ]

(quad) 我们可以知道其价值为:

[omega(b) = prodlimits_{i}^kleft(a_{t_i}+i ight) = prodlimits_{i}^kleft(b_{t_i}+i-t_i ight) ]

(quad) 我们不妨尝试打开这个奇怪的东西,我们发现这个东西是由若干 (b) 乘法和若干 (i-t_{i}) 构成。

(quad) 尝试寻找这个奇怪的东西的组合意义

(quad) 发现,我们假设我们最后... 算了算了,根本说不清楚这个奇怪的组合意义,我还是举例子好了...

(quad)(n = 7)

(quad) 不妨令:

[{a_1,a_2,a_3,a_4,a_5,a_6,a_7} = {1,2,3,4,5,6,7} ]

(quad) 我们考虑某一个 (k=3) 元素子集:

[{a_2,a_4,a_6}={2,4,6},b_2=4,b_4=8,b_6 = 12 ]

(quad) 我们考虑其中的一项:

[b_2(-2)(-3) ]

(quad) 考虑这个 ((-2) imes(-3) = 6) 在干嘛。如果你的脑冻够大的话,你可以认为这是一个映射。

(quad) 也就是对于一个 (k) 元素子集 (b),我们考虑其中选了 (c)(b),那么我们要做的就是把剩下的 (k-c) 个元素映射到某一个元素,具体来说,就是 (b_{t_i})(k-c) 个元素 映射到没有在 (b) 中出现的元素的前 (t_i) 个中的某一个上面。 把不属于 (b) 的元素映射到自身。那 (c) 个元素带权值,并且映射到自身。其中权值为(b_{t_i})的乘积。

(quad) 也就是在上面的例子当中 (n = 7,k = 3,c = 1)。权值为 (b_2 = 2)

(quad) 我们不妨把最后映射到同一个元素的元素合并起来。那么可以得到一个集合的划分,显然,可以证明这个划分对应了一种映射。那么集合拆分的 ( ext{Bell}) 数不就是映射的数目吗?但是注意,我们这里是带符号( ext{Bell}) 数。符号为 ((-1)^{k-c})

(quad) 我们可以这么认为 (k-c = (n-c)-(n-k))

(quad) 也就是说,我们可以认为这个是带符号的第二类斯特林数。

(quad) 也就是:

[B_n = sumlimits_{k=0}^n(-1)^{n-k}egin{Bmatrix}n \ k end{Bmatrix} ]

(quad) 找到这个东西的生成函数的 ( ext{EGF})

[B(x) = e^{1-e^{-x}} ]

(quad) 然后大力 ( ext{exp}) 就可以了。最后用分治 ( ext{NTT}) 算出:

[(x+b_1)(x+b_2)cdotcdotcdot(x+b_n) ]

(quad) 代码的话有手就行,这里放出主要代码...

int n,a[N] ; 
int f[N],g[N] ;
inline vector<int> solve(int l,int r){
	if(l == r) return (vector<int>){a[l],1} ;
	int mid = (l+r)>>1 ;
	vector<int> lans = solve(l,mid) ;
	vector<int> rans = solve(mid+1,r) ;
	int la = lans.size(),lb = rans.size() ;
	int len = la+lb-1,limit = 1 ;
	while(limit < len) limit <<= 1 ;
	for(int i = 0 ; i < la ; i++) f[i] = lans[i] ;
	for(int i = la ; i < limit ; i++) f[i] = 0 ; 
	for(int i = 0 ; i < lb ; i++) g[i] = rans[i] ; 
	for(int i = lb ; i < limit ; i++) g[i] = 0 ;
	Poly::calrev(limit) ;
	Poly::NTT(f,limit,1) ; Poly::NTT(g,limit,1) ;
	for(int i = 0 ; i < limit ; i++) f[i] = mul(f[i],g[i]) ;
	Poly::NTT(f,limit,-1) ;
	vector<int> ans ; ans.resize(len) ; 
	for(int i = 0 ; i < len ; i++) ans[i] = f[i] ;
	return ans ; 
}
int B[N],C[N] ; 
int main(){
	//freopen("in.txt","r",stdin) ;
	prework(4e5) ;
	cin >> n ;
	for(int i = 1 ; i <= n ; i++) scanf("%d",&a[i]),a[i] = add(a[i],i) ;
	vector<int> A = solve(1,n) ;
	for(int i = 1 ; i <= n ; i++) B[i] = (i&1) ? ifac[i] : sub(mod,ifac[i]) ;
	Poly::Exp(B,C,n+1) ;
	int ans = 0 ;
	for(int i = 0 ; i <= n ; i++) ans = add(ans,mul(C[i],mul(fac[i],A[i]))) ;
	cout << sub(ans,1) << endl ; 
	return 0 ; 
}

(quad) 后话

(quad) 对于这种组合意义可以说是奇怪的题目,代数似乎优势很大?不过似乎考场上难题似乎区分度不是很大?所以会不会无所谓?

(quad) 还有一个问题就是,我的常数为什么这么大,( ext{9000ms}) 怒拿最劣解,我不会是被某人传染了吧...完了完了...


(quad mathcal Solution mathcal C:)

(quad) 现在回来看看自己以前的做法真的太辣鸡了,我们有个更好的做法。

(quad) 讲一个常人能想到的 ( ext{dp}) : (f_{i,j}) 代表前 (i) 个数字选出 (j) 个数字的时候的答案。

(quad) 转移显然如下:

[f_{i,j} = f_{i-1,j}+f_{i-1,j-1}(a_{i}+j) ]

(quad) 很好!这个东西没法优化!

(quad) 没办法,我们考虑转移它的定义。我们设 (f_{i,j}) 代表前 (i) 个数字选出 (i-j) 个数字的答案。

(quad) 转移显然如下

[f_{i,j} = f_{i-1,j-1}+f_{i-1,j}(a_{i}+i-j) = f_{i-1,j-1}+f_{i-1,j}(a_{i}+i)-jf_{i-1,j} ]

(quad) 这个东西的组合意义比较自然

  • 不选第 (i) 个数字,代价 ( imes(a_i+i))

  • 选第 (i) 个数字,新建一个组,新建一个组方案数目显然为 ( imes 1)

  • 选第 (i) 个数字,放到前 (j) 个组里面,方案数目显然为 (j),但是这里 ( imes (-j))

(quad) 那么不选若干个数字的答案的生成函数可以直接分治 ( ext{NTT}) 展开。

(quad) 选若干个数的方案就是把若干个球放入盒子的方案数目,就是有符号罢了。很容易得到生成函数为 (e^{1-e^{-x}})

(quad) 把两个生成函数卷积起来就得到了答案。代码不变。


(quad) 上面讲了三种不同的做法,思维的难度一个比一个低,但是想要找到万千解法当中优秀的做法却是十分困难的。很多时候开始思考的做法是复杂的,但是慢慢就能找到本质的做法,这也启示我们面对数学问题不能只会一种做法,只有融合思想,才能用最优雅的方案解出最复杂的题目...

原文地址:https://www.cnblogs.com/jvruotyy/p/13965609.html