[ACNOI2021]《普林斯普的呼唤》卷一

280 篇文章 1 订阅
23 篇文章 0 订阅

题目

题目背景
从欧亚大陆的正中心出发,一直向南走,直到离开陆地、驶上海洋,会遇到一个从来无人涉足的小岛。迅速把船靠在岸边,一旦下船、上岸,船就会自动沉没,就像底下有一双手在拉扯着它。

岛上有唯一一棵没有叶子的树,它被装在了一个漆黑的套子里。在这棵树前,画下失传已久的魔法阵,就能召唤出比人类更高级的存在……

题目描述
魔法阵有 k k k 个祭坛。一共有 n n n 件圣物,每个圣物都要摆在一个祭坛上,每个祭坛上可以摆若干件圣物,但是绝不能一件也不放,否则会引起召唤之物的反噬……

每个圣物有它的能量。一个祭坛的能量,是放置在其上的圣物的能量和。这些能量可以激发祭坛上每一个圣物,所以一个祭坛提供的 “效力” 是圣物数量乘以圣物能量和。

那么,对于所有合法的圣物放置方案,它们的所有祭坛的 “效力” 之和是多少?注意祭坛之间是没有区别的。

数据范围与提示
k ⩽ n ⩽ 1 0 6 k\leqslant n\leqslant 10^6 kn106,圣物的能量 a i ⩽ 1 0 9 a_i\leqslant 10^9 ai109

思路

说人话就是:将 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=1n(j1n1){k1nj}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}=[xim]i=1m1ix1
F n ( x ) = ∏ i = 1 n ( x − i ) F_n(x)=\prod_{i=1}^{n}(x-i) Fn(x)=i=1n(xi) 是一个经典的分治问题:求出 F n ( x ) F_n(x) Fn(x) 后,根据二项式展开求出 F n ( x − n ) F_n(x-n) Fn(xn),二者相乘即得 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} (j1n1){k1nj} 看上去就很像 { n − 1 k } n-1\brace k {kn1} 嘛!想办法把它往上靠试试?

首先处理 j = ( j − 1 ) + 1 j=(j-1)+1 j=(j1)+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} (n1)j=2n(j2n2){k1nj}+j=1n(j1n1){k1nj}
比如 ( n − 1 j − 1 ) { n − j k − 1 } {n-1\choose j-1}{n-j\brace k-1} (j1n1){k1nj},它想变成 { n − 1 k } {n-1\brace k} {kn1} 有如下问题:一是盒子无编号,选出的 ( n − 1 j − 1 ) {n-1\choose j-1} (j1n1) 作为 “一号盒子” 并不妥;二是 j − 1 j-1 j1 可以为 0 0 0,也就是空盒。

有两种解决办法。第一种是分类讨论。则得到 { n − 1 k − 1 } + k { n − 1 k } {n-1\brace k-1}+k{n-1\brace k} {k1n1}+k{kn1},左为空盒情况,右为非空盒情况,由于其余盒子都有可能被 ( n − 1 j − 1 ) {n-1\choose j-1} (j1n1) 选出来,所以乘系数 k k k 即可。

第二种方法是,加一个球。让这 j − 1 j-1 j1 个球与 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} (j2n2){k1nj},完全同理,加一个球得到 { n − 1 k } n-1\brace k {kn1}

所以最终答案就是
( n − 1 ) { n − 1 k } + { n k } (n-1){n-1\brace k}+{n\brace k} (n1){kn1}+{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=0k(1)i(ik)(ki)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;
}

后记

摆好魔法阵的一瞬间,感觉天地都为之色变,定睛望去,太阳似乎在震颤……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值