题目描述
(quad) 传送门
题解
(quad mathcal Solution mathcal A:)
(quad) 毛夫的数学做法
(quad mathcal Solution mathcal B:)
(quad) 我们令:
(quad) 我们考虑 (b) 的一个子集:
(quad) 我们可以知道其价值为:
(quad) 我们不妨尝试打开这个奇怪的东西,我们发现这个东西是由若干 (b) 乘法和若干 (i-t_{i}) 构成。
(quad) 尝试寻找这个奇怪的东西的组合意义。
(quad) 发现,我们假设我们最后... 算了算了,根本说不清楚这个奇怪的组合意义,我还是举例子好了...
(quad) 设 (n = 7)。
(quad) 不妨令:
(quad) 我们考虑某一个 (k=3) 元素子集:
(quad) 我们考虑其中的一项:
(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) 也就是:
(quad) 找到这个东西的生成函数的 ( ext{EGF}) :
(quad) 然后大力 ( ext{exp}) 就可以了。最后用分治 ( ext{NTT}) 算出:
(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) 转移显然如下:
(quad) 很好!这个东西没法优化!
(quad) 没办法,我们考虑转移它的定义。我们设 (f_{i,j}) 代表前 (i) 个数字选出 (i-j) 个数字的答案。
(quad) 转移显然如下
(quad) 这个东西的组合意义比较自然
-
不选第 (i) 个数字,代价 ( imes(a_i+i))
-
选第 (i) 个数字,新建一个组,新建一个组方案数目显然为 ( imes 1)
-
选第 (i) 个数字,放到前 (j) 个组里面,方案数目显然为 (j),但是这里 ( imes (-j))
(quad) 那么不选若干个数字的答案的生成函数可以直接分治 ( ext{NTT}) 展开。
(quad) 选若干个数的方案就是把若干个球放入盒子的方案数目,就是有符号罢了。很容易得到生成函数为 (e^{1-e^{-x}})。
(quad) 把两个生成函数卷积起来就得到了答案。代码不变。
(quad) 上面讲了三种不同的做法,思维的难度一个比一个低,但是想要找到万千解法当中优秀的做法却是十分困难的。很多时候开始思考的做法是复杂的,但是慢慢就能找到本质的做法,这也启示我们面对数学问题不能只会一种做法,只有融合思想,才能用最优雅的方案解出最复杂的题目...