题目
题目背景
从欧亚大陆的正中心出发,一直向南走,直到离开陆地、驶上海洋,会遇到一个从来无人涉足的小岛。迅速把船靠在岸边,一旦下船、上岸,船就会自动沉没,就像底下有一双手在拉扯着它。
岛上有唯一一棵没有叶子的树,它被装在了一个漆黑的套子里。在这棵树前,画下失传已久的魔法阵,就能召唤出比人类更高级的存在……
题目描述
魔法阵有
k
k
k 个祭坛。一共有
n
n
n 件圣物,每个圣物都要摆在一个祭坛上,每个祭坛上可以摆若干件圣物,但是绝不能一件也不放,否则会引起召唤之物的反噬……
每个圣物有它的能量。一个祭坛的能量,是放置在其上的圣物的能量和。这些能量可以激发祭坛上每一个圣物,所以一个祭坛提供的 “效力” 是圣物数量乘以圣物能量和。
那么,对于所有合法的圣物放置方案,它们的所有祭坛的 “效力” 之和是多少?注意祭坛之间是没有区别的。
数据范围与提示
k
⩽
n
⩽
1
0
6
k\leqslant n\leqslant 10^6
k⩽n⩽106,圣物的能量
a
i
⩽
1
0
9
a_i\leqslant 10^9
ai⩽109 。
思路
说人话就是:将 a i a_i ai 划分到 k k k 个无标号的盒子,每个盒子提供贡献 s i z e × s u m size\times sum size×sum 。
可以将
s
u
m
sum
sum 分摊到每个
a
i
a_i
ai 上,枚举
a
i
a_i
ai 所在的盒子的大小是
j
j
j,问题变为
∑
j
=
1
n
(
n
−
1
j
−
1
)
⋅
{
n
−
j
k
−
1
}
⋅
j
\sum_{j=1}^{n}{n-1\choose j-1}\cdot{n-j\brace k-1}\cdot j
j=1∑n(j−1n−1)⋅{k−1n−j}⋅j
其中符号
{
n
m
}
{n\brace m}
{mn} 表示将
n
n
n 个有标号的球划分到
m
m
m 个无标号的盒子,无空盒的方案数。
在这道题里,我学到了一点:不要考虑组合数学,生成函数干就完了!于是显然我们只需要求出
{
i
m
}
{i\brace m}
{mi},可以发现这东西的生成函数是
{
i
m
}
=
[
x
i
−
m
]
∏
i
=
1
m
1
1
−
i
x
{i\brace m}=[x^{i-m}]\prod_{i=1}^{m}\frac{1}{1-ix}
{mi}=[xi−m]i=1∏m1−ix1
而
F
n
(
x
)
=
∏
i
=
1
n
(
x
−
i
)
F_n(x)=\prod_{i=1}^{n}(x-i)
Fn(x)=∏i=1n(x−i) 是一个经典的分治问题:求出
F
n
(
x
)
F_n(x)
Fn(x) 后,根据二项式展开求出
F
n
(
x
−
n
)
F_n(x-n)
Fn(x−n),二者相乘即得
F
2
n
(
x
)
F_{2n}(x)
F2n(x) 。这是
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 的。
于是我就写完了,然而
N
T
T
\rm NTT
NTT 常数太大,根本无法通过。考虑是否有更好的做法呢?没错,用组合数学。成功地打了自己的脸。
其实 ( n − 1 j − 1 ) { n − j k − 1 } {n-1\choose j-1}{n-j\brace k-1} (j−1n−1){k−1n−j} 看上去就很像 { n − 1 k } n-1\brace k {kn−1} 嘛!想办法把它往上靠试试?
首先处理
j
=
(
j
−
1
)
+
1
j=(j-1)+1
j=(j−1)+1 就可以放入组合数,得到
(
n
−
1
)
∑
j
=
2
n
(
n
−
2
j
−
2
)
{
n
−
j
k
−
1
}
+
∑
j
=
1
n
(
n
−
1
j
−
1
)
{
n
−
j
k
−
1
}
(n-1)\sum_{j=2}^{n}{n-2\choose j-2}{n-j\brace k-1}+\sum_{j=1}^{n}{n-1\choose j-1}{n-j\brace k-1}
(n−1)j=2∑n(j−2n−2){k−1n−j}+j=1∑n(j−1n−1){k−1n−j}
比如
(
n
−
1
j
−
1
)
{
n
−
j
k
−
1
}
{n-1\choose j-1}{n-j\brace k-1}
(j−1n−1){k−1n−j},它想变成
{
n
−
1
k
}
{n-1\brace k}
{kn−1} 有如下问题:一是盒子无编号,选出的
(
n
−
1
j
−
1
)
{n-1\choose j-1}
(j−1n−1) 作为 “一号盒子” 并不妥;二是
j
−
1
j-1
j−1 可以为
0
0
0,也就是空盒。
有两种解决办法。第一种是分类讨论。则得到 { n − 1 k − 1 } + k { n − 1 k } {n-1\brace k-1}+k{n-1\brace k} {k−1n−1}+k{kn−1},左为空盒情况,右为非空盒情况,由于其余盒子都有可能被 ( n − 1 j − 1 ) {n-1\choose j-1} (j−1n−1) 选出来,所以乘系数 k k k 即可。
第二种方法是,加一个球。让这 j − 1 j-1 j−1 个球与 n + 1 n+1 n+1 号球放在一起。这样就没有空盒了,并且让其成为 “一号盒子” 也是合乎情理的。得到 { n k } {n\brace k} {kn} 。
当然,根据递推式,二者是等价的。不过下面这个看上去要厉害一点 😂
然后看看 ( n − 2 j − 2 ) { n − j k − 1 } {n-2\choose j-2}{n-j\brace k-1} (j−2n−2){k−1n−j},完全同理,加一个球得到 { n − 1 k } n-1\brace k {kn−1} 。
所以最终答案就是
(
n
−
1
)
{
n
−
1
k
}
+
{
n
k
}
(n-1){n-1\brace k}+{n\brace k}
(n−1){kn−1}+{kn}
乘上
∑
a
i
\sum a_i
∑ai 就是需要的输出。而计算一个
{
n
k
}
n\brace k
{kn} 很简单,先让盒子有标号,用容斥去掉有空盒的情况,然后除以
k
!
k!
k! 即可去掉编号。形式化地有
{
n
k
}
=
1
k
!
∑
i
=
0
k
(
−
1
)
i
⋅
(
k
i
)
⋅
(
k
−
i
)
n
{n\brace k}=\frac{1}{k!}\sum_{i=0}^{k}(-1)^{i}\cdot{k\choose i}\cdot(k-i)^n
{kn}=k!1i=0∑k(−1)i⋅(ik)⋅(k−i)n
如果用快速幂,这是
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 的;如果线性筛则可以
O
(
n
)
\mathcal O(n)
O(n) 。但是
1
0
6
10^6
106 就没必要了吧……
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
(c == '-') ? (f = -f) : 0;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(int x){
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int Mod = 998244353;
inline void modAddUp(int &x,const int &y){
if((x += y) >= Mod) x -= Mod;
}
inline int qkpow(int_ b,int q){
int_ a = 1;
for(; q; q>>=1,b=b*b%Mod)
if(q&1) a = a*b%Mod;
return static_cast<int>(a);
}
const int MAXN = 1000005;
int inv[MAXN], ck[MAXN];
int getStelin(int n,int m){
int_ sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0;
int i = 0; for(; i+3<=m; i+=4){
sum0 += int_(ck[i])*qkpow(m-i,n)%Mod;
sum1 += int_(ck[i+1])*qkpow(m-i-1,n)%Mod;
sum2 += int_(ck[i+2])*qkpow(m-i-2,n)%Mod;
sum3 += int_(ck[i+3])*qkpow(m-i-3,n)%Mod;
}
switch(m&3){
case 2: sum2 += int_(ck[i+2])*qkpow(m-i-2,n)%Mod;
case 1: sum1 += int_(ck[i+1])*qkpow(m-i-1,n)%Mod;
case 0: sum0 += int_(ck[i])*qkpow(m-i,n)%Mod;
}
return int(((sum0-sum1+sum2-sum3)%Mod+Mod)%Mod);
}
int main(){
int n = readint(), k = readint();
inv[1] = ck[0] = 1, ck[1] = k;
rep(i,2,k){
inv[i] = int(int_(Mod-Mod/i)*inv[Mod%i]%Mod);
ck[i] = int(int_(k+1-i)*ck[i-1]%Mod*inv[i]%Mod);
}
int_ sum = 0, coe = getStelin(n,k);
coe = (coe+int_(n-1)*getStelin(n-1,k))%Mod;
rep(i,1,n) sum += readint();
sum = sum%Mod*coe%Mod;
rep(i,2,k) sum = sum*inv[i]%Mod;
printf("%lld\n",sum);
return 0;
}
后记
摆好魔法阵的一瞬间,感觉天地都为之色变,定睛望去,太阳似乎在震颤……