【2020杭电多校round5】HDU6818 Array Repairing

题目大意

题目链接

对于一个给定的正整数\(n\),随机生成一个正整数序列\(a[1\dots n]\),满足\(\forall i\in[1,n]:a_i\in[1,n]\)。注意,\(a\)不一定是排列。

你需要通过若干次操作,使得\(\forall i\in[1,n],a_i=i\)。你可以进行的操作有两种:

  1. 选择一对位置\(i,j\in[1,n]\)\(i\neq j\),交换\(a_i,a_j\)。一次操作花费为\(1\)
  2. 选择一个位置\(i\in[1,n]\)和一个数值\(v\in[1,n]\),令\(a_i:=v\)。一次操作花费为\(k\)

例如,如果你要进行\(5\)次1操作和\(4\)次2操作,则总花费为\(4\times k+5\)

\(\text{cost}_k(a)\)表示在参数\(k\)的条件下,将序列\(a\)变成\(1,2,\dots ,n\)的最小花费。对\(k=0,1,2\),你需要分别求出\(\text{cost}_k(a)\)的期望值\(E(\text{cost}_k(a))\)。输出\(\bmod 998244353\)意义下的值。

给定\(N\)。对所有\(n\in[1,N]\)分别求答案。也就是说,你一共需要输出\(3N\)个数。

数据范围:\(1\leq N\leq 5\times10^5\)

本题题解

我们称把\(a\)序列变成\(1,2,\dots,n\)的过程为“还原”这个序列。一个位置\(i\)“已经还原”,就是指\(a_i=i\)

首先,\(k=0\)时,代价一定为\(0\)

subtask \(k=1\)

考虑\(k=1\)的情况。我们从每个\(i\)\(a_i\)连一条有向边,则能得到一个基环(内向)树森林。发现,对每个环,设其长度为\(\text{len}\),我们使用\(\text{len}-1\)次1操作可以将其还原(环上每个点\(a_i=i\))。剩下的部分只能用2操作完成。容易证明这样花费是最小的。

对于一个长度为\(n\)的序列\(a\),设它对应的基环树森林里,环的数量为\(c\)。则将\(a\)序列还原的最小花费就是\(n-c\)。注意,自环也算,也就是说“环的数量”和“连通块的数量”是等价的。

要求期望,我们乘以序列的总数\(n^n\)以后,就转化为求所有序列的最小花费之和。又因为最小花费为\(n-\text{环的数量}\)\(\sum(n-\text{环的数量})=\sum n-\sum\text{环的数量}=n^n\cdot n-\sum \text{环的数量}\)。问题转化为求\(\sum\text{环的数量}\),也就是所有基环树森林,环的数量之和,记为\(h(n)\)

可以写出式子:\(h(n)=\sum_{i=1}^{n}(i-1)!{n\choose i}n^{n-i}\)。含义:\((i-1)!\)表示长度为\(i\)的环有多少种,\({n\choose i}\)表示选出一些位置作为这个环,\(n^{n-i}\)表示其他位置随意。不过要对每个\(n\)求一次,时间复杂度是\(O(N^2)\)的,无法承受。比赛时,你可以暴力求出前几个\(h\)值,然后去oeis上找到这个序列:A190314。不过还有更好的方法。

考虑用生成函数做。设\(f(n)\)表示\(n\)个点的基环树数量(这里指的是一棵\(n\)个节点的、完整的基环树有多少种,而不是基环树森林)。这个\(f\)我们暂时还不会求。

再设一个\(g(n)\)表示\(n\)个点的基环树森林数量。这是很好求的,就等于\(n^n\)。设\(F(x),G(x)\)分别是\(f,g\)指数型生成函数(即\(F(x)=\sum_{i=0}^{\infty}f(x)\frac{x^i}{i!}\)\(G\)同理)。则根据指数型生成函数的理论,可知:\(G(x)=\exp(F(x))\)。所以\(F(x)=\ln(G(x))\)。这样,通过做多项式\(\ln\),我们就求出了\(f\)序列。

知道了\(f,g\)以后。设\(h\)的指数型生成函数为\(H(x)\),则\(H(x)=F(x)G(x)\)。这相等于,考虑每个基环树,对总数的贡献。单独拿出一棵\(i\)个节点的基环树,首先这样的基环树有\(f(i)\)个,剩下的部分可以任意形成一个基环树森林,方案数就是\(g(n-i)\)(注意,特别地,这里我们认为\(g(0)=1\)),而从\(n\)个点里选出这\(i\)个点的方案数是\({n\choose i}\),所以\(h(n)=\sum_{i=0}^{n}{n\choose i}f(i)g(n-i)\)。这正是指数型生成函数卷积的定义。

总结来说,我们先搞一个序列\(g(n)=n^n\)。然后\(G(x)\cdot \ln G(x)\),就是我们要求的环的总数\(h\)了。答案就是\(\displaystyle\frac{n^n\cdot n-h(n)}{n^n}\)。(除以\(n^n\)是因为要求期望)。

时间复杂度\(O(N\log N)\)

subtask \(k=2\)

\(k=2\)时,环上的点,我们还是用1操作来完成。考虑剩下的点。相当于要把每个点,变成它的儿子上面现在的值(显然每个儿子上的值是一样的)。所以对每个点,我们随便挑个儿子和它做交换(操作1)。直到叶子节点,没有儿子了,我们再用操作2。总的代价是\(n-\text{环的数量}+\text{叶子数量}\)

环的数量我们已经会求了。考虑叶子数量。

考虑每个点作为叶子时,对总数的贡献。它的贡献数,就等于钦定它是叶子后,其他点组成基环树森林的总方案数。也就是\((n-1)^{n-1}\),此外,这个点自己还要随便选一个父亲,所以要乘以\(n-1\),也就是\((n-1)^{n}\)。这个思想挺妙的,把求所有基环树的叶子树之和,转化为了求每个叶子出现在多少基环树里。

所以所有叶子的总贡献就是\(n\cdot (n-1)^n\)。答案就是:\(\displaystyle\frac{n^n\cdot n-h(n)+n\cdot (n-1)^n}{n^n}\)

时间复杂度\(O(N\log N)\)

参考代码

\(\text{NTT}\)和多项式\(\ln\)的部分省略了,自己贴板子吧。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN=5e5;
const int MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int fac[MAXN+5],ifac[MAXN+5];
inline int comb(int n,int k){
	if(n<k)return 0;
	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
}
void facinit(int lim=MAXN){
	fac[0]=1;
	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
	ifac[lim]=pow_mod(fac[lim],MOD-2);
	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
}

int f[MAXN+5],g[MAXN+5],h[MAXN+5];

int main() {
	facinit();
	
	for(int i=1;i<=MAXN;++i){
		g[i]=(ll)pow_mod(i,i)*ifac[i]%MOD;
	}
	g[0]=1;
	Ln::main(g,f,MAXN+1);
	Mul::main(g,f,h,MAXN+1);
	for(int i=0;i<=MAXN;++i){
		h[i]=(ll)h[i]*fac[i]%MOD;
	}
	int n;cin>>n;
	for(int i=1;i<=n;++i){
		int ans=h[i]%MOD;
		int tot=pow_mod(i,i);
		ans=mod2((ll)tot*i%MOD - ans);
		ans=(ll)ans*pow_mod(tot,MOD-2)%MOD;
		cout<<0<<" "<<ans<<" ";
		int x=pow_mod(i-1,i);
		int y=pow_mod(i,i-1);
		x=(ll)x*pow_mod(y,MOD-2)%MOD;
		add(ans,x);
		cout<<ans<<endl;
	}
	return 0;
}
posted @ 2020-08-04 22:10  duyiblue  阅读(339)  评论(2编辑  收藏  举报