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

  • 相关阅读:
    HDOJ 2871 Memory Control(线段树区间合并与查询)
    POJ 3468 A Simple Problem with Integers(线段树成段更新)
    POJ 2923 Relocation(状态压缩 + 两次DP)
    POJ 1436 Horizontally Visible Segments(线段树区间染色查询)
    POJ 2528 Mayor's posters(离散化的线段树)
    HDOJ 3308 LCIS(线段树区间合并与查询)
    异常处理的指导原则
    CSC命令
    .NET命名空间举例
    System.DateTime
  • 原文地址:https://www.cnblogs.com/jvruotyy/p/13965609.html
Copyright © 2011-2022 走看看